Need help fixing logic for custom Spike/P-Gap Indicator (Breakout & Retest logic)

 

Hello everyone,

I am posting this request again because I previously asked a similar question but unfortunately didn't reach a solution. I am working on a custom indicator for MQL4 based on a "Spike" and "P-Gap" strategy, but the code is not behaving according to my defined logic.

I would really appreciate it if someone could review my code and point out where the logic is flawed or what needs to be corrected and how should be corrected to match the rules below.

Here is the exact logic of the strategy:

1. Spike Identification:

  • Sequence: We need at least 3 consecutive candles of the same color (Bullish or Bearish). More than 3 is also acceptable.
  • Breakout: Each candle in this sequence must break out of the previous candle (e.g., for a bullish spike, the high/close must be higher than the previous one).
    //+------------------------------------------------------------------+
    //|                                            Spike_FVG_Indicator.mq4 |
    //|                                                                    |
    //+------------------------------------------------------------------+
    #property copyright "Spike FVG Strategy"
    #property link      ""
    #property version   "3.10"
    #property strict
    #property indicator_chart_window
    #property indicator_buffers 2
    
    
    input int MinSpikeCandlesCount = 3;        
    input double MinBodyPercent = 60.0;        
    input int MinSpikeSize = 10;               
    input int SpikeSearchRange = 300;          
    input color BuySignalColor = clrLime;      
    input color SellSignalColor = clrRed;      
    input color EntryLineColor = clrYellow;    
    input color SLLineColor = clrRed;          
    input color TPLineColor = clrLime;         
    input int ArrowSize = 1;                   
    input bool ShowSLTP = true;                
    input int SLTPLineLength = 5;              
    input bool AlertsEnabled = true;           
    
    
    double BuySignal[];
    double SellSignal[];
    
    
    datetime lastBuySignalTime = 0;
    datetime lastSellSignalTime = 0;
    double pipValue;
    
    //+------------------------------------------------------------------+
    int OnInit()
    {
       SetIndexBuffer(0, BuySignal);
       SetIndexStyle(0, DRAW_ARROW, STYLE_SOLID, ArrowSize, BuySignalColor);
       SetIndexArrow(0, 233);
       SetIndexLabel(0, "Buy Signal");
       SetIndexEmptyValue(0, EMPTY_VALUE);
       
       SetIndexBuffer(1, SellSignal);
       SetIndexStyle(1, DRAW_ARROW, STYLE_SOLID, ArrowSize, SellSignalColor);
       SetIndexArrow(1, 234);
       SetIndexLabel(1, "Sell Signal");
       SetIndexEmptyValue(1, EMPTY_VALUE);
       
    
       if(Digits == 5 || Digits == 3)
          pipValue = Point * 10;
       else
          pipValue = Point;
       
       return(INIT_SUCCEEDED);
    }
    
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
    {
       ObjectsDeleteAll(0, "SLTP_");
    }
    
    //+------------------------------------------------------------------+
    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 < MinSpikeCandlesCount + 20) return(0);
       
       int limit;
       
       if(prev_calculated == 0)
       {
          ArrayInitialize(BuySignal, EMPTY_VALUE);
          ArrayInitialize(SellSignal, EMPTY_VALUE);
          limit = rates_total - 10;
       }
       else
       {
          limit = rates_total - prev_calculated + 5;
       }
       
       for(int i = MathMin(limit, rates_total - 10); i >= 1; i--)
       {
          CheckForEntry(i);
       }
       
       return(rates_total);
    }
    
    //+------------------------------------------------------------------+
    void CheckForEntry(int currentBar)
    {
       double spikeHigh, spikeLow;
       int spikeLength;
       
       
       if(IsBearishCandle(currentBar))
       {
          for(int searchBar = currentBar + 1; searchBar <= currentBar + SpikeSearchRange && searchBar < Bars - MinSpikeCandlesCount; searchBar++)
          {
             if(DetectBullishSpike(searchBar, spikeHigh, spikeLow, spikeLength))
             {
                if(IsFirstBearishAfterSpike(currentBar, searchBar))
                {
                   if(Time[currentBar] != lastBuySignalTime)
                   {
                      BuySignal[currentBar] = Low[currentBar] - 10 * _Point;
                      
                      if(ShowSLTP)
                         DrawSLTP(currentBar, true, spikeLow);
                      
                      if(AlertsEnabled && currentBar == 1)
                         Alert("buy signal - Spike FVG  ", Symbol());
                      
                      lastBuySignalTime = Time[currentBar];
                      return;
                   }
                }
             }
          }
       }
       
       
       if(IsBullishCandle(currentBar))
       {
          for(int searchBar = currentBar + 1; searchBar <= currentBar + SpikeSearchRange && searchBar < Bars - MinSpikeCandlesCount; searchBar++)
          {
             if(DetectBearishSpike(searchBar, spikeHigh, spikeLow, spikeLength))
             {
                if(IsFirstBullishAfterSpike(currentBar, searchBar))
                {
                   if(Time[currentBar] != lastSellSignalTime)
                   {
                      SellSignal[currentBar] = High[currentBar] + 10 * _Point;
                      
                      if(ShowSLTP)
                         DrawSLTP(currentBar, false, spikeHigh);
                      
                      if(AlertsEnabled && currentBar == 1)
                         Alert("sell signal - Spike FVG ", Symbol());
                      
                      lastSellSignalTime = Time[currentBar];
                      return;
                   }
                }
             }
          }
       }
    }
    
    //+------------------------------------------------------------------+
    bool IsBullishCandle(int bar)
    {
       return (Close[bar] > Open[bar]);
    }
    
    //+------------------------------------------------------------------+
    bool IsBearishCandle(int bar)
    {
       return (Close[bar] < Open[bar]);
    }
    
    //+------------------------------------------------------------------+
    double GetBodyPercent(int bar)
    {
       double totalRange = High[bar] - Low[bar];
       if(totalRange == 0) return 0;
       
       double bodySize = MathAbs(Close[bar] - Open[bar]);
       return (bodySize / totalRange) * 100.0;
    }
    
    //+------------------------------------------------------------------+
    bool IsBullishBreakout(int bar)
    {
       if(bar + 1 >= Bars) return false;
       return (High[bar] > High[bar + 1]);
    }
    
    //+------------------------------------------------------------------+
    bool IsBearishBreakout(int bar)
    {
       if(bar + 1 >= Bars) return false;
       return (Low[bar] < Low[bar + 1]);
    }
    
    //+------------------------------------------------------------------+
    bool HasFVG(int bar, bool isBullish)
    {
       if(bar + 1 >= Bars || bar - 1 < 0) return false;
       
       if(isBullish)
          return (Low[bar - 1] > High[bar + 1]);
       else
          return (High[bar - 1] < Low[bar + 1]);
    }
    
    //+------------------------------------------------------------------+
    // GetSpikeSizeInPips
    //+------------------------------------------------------------------+
    double GetSpikeSizeInPips(double spikeHigh, double spikeLow)
    {
       return (spikeHigh - spikeLow) / pipValue;
    }
    
    //+------------------------------------------------------------------+
    bool DetectBullishSpike(int startBar, double &spikeHigh, double &spikeLow, int &spikeLength)
    {
       if(startBar + MinSpikeCandlesCount >= Bars) return false;
       
       bool hasFVG = false;
       spikeHigh = 0;
       spikeLow = 999999;
       spikeLength = 0;
       
       for(int i = 0; i < 50; i++)
       {
          int bar = startBar + i;
          if(bar >= Bars - 1) break;
          
          if(!IsBullishCandle(bar)) break;
          if(!IsBullishBreakout(bar)) break;
          if(GetBodyPercent(bar) < MinBodyPercent) break;
          
          spikeLength++;
          
          if(HasFVG(bar, true))
             hasFVG = true;
          
          if(High[bar] > spikeHigh) spikeHigh = High[bar];
          if(Low[bar] < spikeLow) spikeLow = Low[bar];
       }
       
       
       if(spikeLength < MinSpikeCandlesCount) return false;
       if(!hasFVG) return false;
       
       
       double spikeSizePips = GetSpikeSizeInPips(spikeHigh, spikeLow);
       if(spikeSizePips < MinSpikeSize) return false;
       
       return true;
    }
    
    //+------------------------------------------------------------------+
    bool DetectBearishSpike(int startBar, double &spikeHigh, double &spikeLow, int &spikeLength)
    {
       if(startBar + MinSpikeCandlesCount >= Bars) return false;
       
       bool hasFVG = false;
       spikeHigh = 0;
       spikeLow = 999999;
       spikeLength = 0;
       
       for(int i = 0; i < 50; i++)
       {
          int bar = startBar + i;
          if(bar >= Bars - 1) break;
          
          if(!IsBearishCandle(bar)) break;
          if(!IsBearishBreakout(bar)) break;
          if(GetBodyPercent(bar) < MinBodyPercent) break;
          
          spikeLength++;
          
          if(HasFVG(bar, false))
             hasFVG = true;
          
          if(High[bar] > spikeHigh) spikeHigh = High[bar];
          if(Low[bar] < spikeLow) spikeLow = Low[bar];
       }
       
       
       if(spikeLength < MinSpikeCandlesCount) return false;
       if(!hasFVG) return false;
       
       
       double spikeSizePips = GetSpikeSizeInPips(spikeHigh, spikeLow);
       if(spikeSizePips < MinSpikeSize) return false;
       
       return true;
    }
    
    //+------------------------------------------------------------------+
    bool IsFirstBearishAfterSpike(int currentBar, int spikeStartBar)
    {
       for(int i = spikeStartBar - 1; i > currentBar; i--)
       {
          if(IsBearishCandle(i))
             return false;
       }
       return true;
    }
    
    //+------------------------------------------------------------------+
    bool IsFirstBullishAfterSpike(int currentBar, int spikeStartBar)
    {
       for(int i = spikeStartBar - 1; i > currentBar; i--)
       {
          if(IsBullishCandle(i))
             return false;
       }
       return true;
    }
    
    //+------------------------------------------------------------------+
    void DrawSLTP(int entryBar, bool isBuy, double slLevel)
    {
       string prefix = "SLTP_" + IntegerToString(entryBar) + "_" + IntegerToString((int)Time[entryBar]);
       
       double entryPrice, slPrice, tpPrice, slDistance;
       
       if(isBuy)
       {
          entryPrice = Close[entryBar];
          slPrice = slLevel - 5 * _Point;
          slDistance = entryPrice - slPrice;
          tpPrice = entryPrice + slDistance;
       }
       else
       {
          entryPrice = Close[entryBar];
          slPrice = slLevel + 5 * _Point;
          slDistance = slPrice - entryPrice;
          tpPrice = entryPrice - slDistance;
       }
       
       datetime timeStart = Time[entryBar];
       int endBar = entryBar - SLTPLineLength;
       if(endBar < 0) endBar = 0;
       datetime timeEnd = Time[endBar];
       
       CreateTrendLine(prefix + "_Entry", timeStart, entryPrice, timeEnd, entryPrice, EntryLineColor, 2, STYLE_SOLID);
       CreateTrendLine(prefix + "_SL", timeStart, slPrice, timeEnd, slPrice, SLLineColor, 1, STYLE_DOT);
       CreateTrendLine(prefix + "_TP", timeStart, tpPrice, timeEnd, tpPrice, TPLineColor, 1, STYLE_DOT);
       
       DrawSmallLabel(prefix + "_SL_Lbl", timeEnd, slPrice, "SL", SLLineColor);
       DrawSmallLabel(prefix + "_TP_Lbl", timeEnd, tpPrice, "TP", TPLineColor);
       DrawSmallLabel(prefix + "_Entry_Lbl", timeEnd, entryPrice, "E", EntryLineColor);
    }
    
    //+------------------------------------------------------------------+
    void CreateTrendLine(string name, datetime t1, double p1, datetime t2, double p2, color clr, int width, int style)
    {
       if(ObjectFind(0, name) < 0)
       {
          ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
          ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
          ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
          ObjectSetInteger(0, name, OBJPROP_STYLE, style);
          ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
          ObjectSetInteger(0, name, OBJPROP_RAY_LEFT, false);
          ObjectSetInteger(0, name, OBJPROP_BACK, true);
       }
    }
    
    //+------------------------------------------------------------------+
    void DrawSmallLabel(string name, datetime time, double price, string text, color clr)
    {
       if(ObjectFind(0, name) < 0)
       {
          ObjectCreate(0, name, OBJ_TEXT, 0, time, price);
          ObjectSetString(0, name, OBJPROP_TEXT, text);
          ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
          ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 6);
          ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
          ObjectSetInteger(0, name, OBJPROP_BACK, true);
       }
    }
    //+------------------------------------------------------------------+ 

  • P-Gap: Within these 3+ candles, at least one of them must contain a "P-Gap" (Pressure Gap).
  • Validation Rules:
    • Size: The total "Spike" movement must be larger than the  Spike Size  parameter defined in the settings.
    • Body Ratio: To ensure momentum, the body of these candles must be at least 65% of their total length.

2. Entry & Trade Management:

  • Entry Signal: The entry is triggered on the first candle that moves against the spike direction (pullback) and touches the "back of the spike" area (Retest).
  • Order: This should activate a Limit Order.
  • Stop Loss (SL): Placed behind the origin/start of the Spike area.
  • Take Profit (TP): Set at a Risk-to-Reward ratio of 1:1.

The Problem:
Currently, the indicator does not correctly identify the setup or place the signals based on these exact conditions.

Could you please take a look at the attached code and guide me on which part needs to be modified? Your help would be a huge lifesaver for me as I have been stuck on this for a while I attacked a screen shot of correct signal.

Thank you very much in advance!

Step on New Rails: Custom Indicators in MQL5
Step on New Rails: Custom Indicators in MQL5
  • 2009.11.23
  • www.mql5.com
I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.
 
Mohammad Dehghani:
Sequence: We need at least 3 consecutive candles of the same color (Bullish or Bearish). More than 3 is also acceptable.

You seem to have a large amount of code that is somewhat convoluted. I would take it from the top and focus on identifying the 3 bar pattern for starters. Perhaps this indicator's code will help:

Files:
 

Here's another indicator that is likely more aligned with your bar pattern.

For your purposes, you should replace:

if ((CloseBar3 < OpenBar3) && (CloseBar2 < OpenBar2) && (CloseBar1 > OpenBar1))

with:

if ((CloseBar3 < CloseBar2) && (CloseBar2 < CloseBar1))

and do the same for up bar conditions.

Based on your OP, you need to compare Lows for down bars/Highs for up bars as well. Just add the Low/High-supporting code throughout the indicator code.

Files: