Help Debugging My Custom SP2L (Spike and 2 Legs) Indicator – Getting Too Many False Signals, Especially in Ranging Markets

 

Hi everyone,

I’ve written a custom indicator called SP2L (Spike and 2 Legs), and I’d really appreciate your help in identifying where my logic might be flawed. The core idea is as follows:

  • A "spike" is detected when at least three consecutive candles of the same color each break the high/low of the previous candle (depending on direction).
  • Additionally, within one of these candles, there must be a pressure gap (a significant wick or imbalance).
  • The entry signal is generated on the first counter-trend candle that touches the back of the spike’s origin zone (i.e., the high/low of the first candle in the spike sequence).
  • Stop Loss is placed behind the spike origin zone, and Take Profit is set 1:1 (RR = 1).
  • I’m trading with 0.01 lot, targeting ~$5 profit per trade.

However, my current implementation is producing too many signals, many of which seem low-probability—especially in ranging markets—and often trigger inside consolidation zones, leading to frequent losses.

Could you please help me:

  1. Identify logical flaws in how I’m detecting the "spike + pressure gap + counter-trend entry"?
  2. Suggest ways to filter out false signals in ranging conditions (e.g., using ATR, ADX, or price structure filters)?
  3. Optimize the entry/exit logic so signals are higher quality, rare, and aligned with my $5 profit goal on 0.01 lots?

I’m happy to share the full code if needed—but first, I’d like to make sure the core logic design is sound.

Thanks in advance for your insights!

//+------------------------------------------------------------------+
//|                                                         SP2L.mq4 |
//|                                  Spike and 2 Legs (FVG Entry)    |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "AI Assistant"
#property link      ""
#property version   "2.00"
#property strict

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1  clrBlue
#property indicator_color2  clrRed
#property indicator_width1  1
#property indicator_width2  1

//--- Buffers
double BuyArrowBuffer[];
double SellArrowBuffer[];

//--- Inputs
input bool   UseAlerts      = true;  // فعال سازی هشدار (Alert)
input bool   UseSound       = true;  // صدای هشدار
input double RiskReward     = 1.0;   // ریسک به ریوارد (پیش فرض 1)
input int    MaxLookBack    = 30;    // تا چند کندل قبل دنبال اسپایک بگردد؟

//--- Global variables
datetime LastAlertTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0,BuyArrowBuffer);
   SetIndexStyle(0,DRAW_ARROW);
   SetIndexArrow(0,233); // فلش بالا
   SetIndexLabel(0,"Buy Setup");

   SetIndexBuffer(1,SellArrowBuffer);
   SetIndexStyle(1,DRAW_ARROW);
   SetIndexArrow(1,234); // فلش پایین
   SetIndexLabel(1,"Sell Setup");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, "SP2L_", 0, OBJ_TREND);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
   int limit = rates_total - prev_calculated;
   if(prev_calculated > 0) limit++;
   if(limit > rates_total - MaxLookBack - 5) limit = rates_total - MaxLookBack - 5;

   for(int i = limit; i >= 0; i--)
     {
      BuyArrowBuffer[i] = 0;
      SellArrowBuffer[i] = 0;

      // ما در کندل i هستیم. باید به عقب نگاه کنیم ببینیم آیا اسپایکی بوده که الان پولبک خورده؟
      // k ایندکس آخرین کندل اسپایک است (کندل سوم)
      
      for(int k = i + 1; k <= i + MaxLookBack; k++)
        {
         // --------------------------
         // چک کردن سناریوی خرید (BUY)
         // --------------------------
         // اسپایک: کندل k, k+1, k+2 باید سبز باشند
         if(close[k] > open[k] && close[k+1] > open[k+1] && close[k+2] > open[k+2])
           {
            // شرط بریک اوت: هر کندل باید بالاتر از کلوز قبلی بسته شده باشد (قدرت)
            if(close[k] > close[k+1] && close[k+1] > close[k+2])
              {
               // شرط مهم: گپ فشار (FVG)
               // در خرید: Low کندل سوم (k) باید بالاتر از High کندل اول (k+2) باشد
               double gapTop = low[k];
               double gapBottom = high[k+2]; // اینجا نقطه ورود ماست (Entry)
               
               if(gapTop > gapBottom) // اگر گپ وجود داشت
                 {
                  // حالا چک میکنیم آیا کندل فعلی (i) به این ناحیه (gapBottom) رسیده است؟
                  // شرط ورود: Low کندل i باید خط Entry را تاچ کرده باشد یا رد کرده باشد
                  if(low[i] <= gapBottom && high[i] > gapBottom) 
                    {
                     // فیلتر رنگ: کندل باید نزولی (خلاف جهت) باشد که نشانه پولبک است
                     if(close[i] < open[i])
                       {
                        // شرط حیاتی: چک کنیم که در کندل‌های بین k و i، این ناحیه قبلا تاچ نشده باشد (Fresh Zone)
                        bool isFresh = true;
                        for(int m = i + 1; m < k; m++)
                          {
                           if(low[m] <= gapBottom)
                             {
                              isFresh = false;
                              break;
                             }
                          }

                        if(isFresh)
                          {
                           // سیگنال معتبر است
                           BuyArrowBuffer[i] = low[i] - 10 * Point;
                           
                           double entryPrice = gapBottom;
                           double slPrice = low[k+2]; // استاپ زیر کندل اول (شروع حرکت)
                           // محاسبه TP یک به یک
                           double risk = entryPrice - slPrice;
                           double tpPrice = entryPrice + (risk * RiskReward);

                           DrawLines("Buy", i, time[i], entryPrice, slPrice, tpPrice);
                           
                           if(i == 0 && UseAlerts && time[0] != LastAlertTime)
                             {
                              Alert("SP2L: Buy Order triggered on ", Symbol());
                              if(UseSound) PlaySound("alert.wav");
                              LastAlertTime = time[0];
                             }
                           // وقتی یک ستاپ پیدا شد برای این کندل، دیگه عقب تر نگرد
                           break; 
                          }
                       }
                    }
                 }
              }
           }

         // --------------------------
         // چک کردن سناریوی فروش (SELL)
         // --------------------------
         if(close[k] < open[k] && close[k+1] < open[k+1] && close[k+2] < open[k+2])
           {
            if(close[k] < close[k+1] && close[k+1] < close[k+2])
              {
               // گپ فشار در فروش: High کندل سوم (k) باید پایین تر از Low کندل اول (k+2) باشد
               double gapBottomSell = high[k];
               double gapTopSell = low[k+2]; // نقطه ورود (Entry)
               
               if(gapBottomSell < gapTopSell) // گپ وجود دارد
                 {
                  // چک کردن تاچ شدن توسط کندل i
                  if(high[i] >= gapTopSell && low[i] < gapTopSell)
                    {
                     // کندل باید صعودی (خلاف جهت) باشد
                     if(close[i] > open[i])
                       {
                        // چک کردن Fresh بودن ناحیه
                        bool isFreshSell = true;
                        for(int m = i + 1; m < k; m++)
                          {
                           if(high[m] >= gapTopSell)
                             {
                              isFreshSell = false;
                              break;
                             }
                          }

                        if(isFreshSell)
                          {
                           SellArrowBuffer[i] = high[i] + 10 * Point;
                           
                           double entryPrice = gapTopSell;
                           double slPrice = high[k+2]; // استاپ بالای کندل اول
                           
                           double risk = slPrice - entryPrice;
                           double tpPrice = entryPrice - (risk * RiskReward);

                           DrawLines("Sell", i, time[i], entryPrice, slPrice, tpPrice);

                           if(i == 0 && UseAlerts && time[0] != LastAlertTime)
                             {
                              Alert("SP2L: Sell Order triggered on ", Symbol());
                              if(UseSound) PlaySound("alert.wav");
                              LastAlertTime = time[0];
                             }
                           break;
                          }
                       }
                    }
                 }
              }
           }
        }
     }
   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Helper to draw entry, sl, tp lines                               |
//+------------------------------------------------------------------+
void DrawLines(string type, int index, datetime time, double entry, double sl, double tp)
  {
   string prefix = "SP2L_" + type + "_" + TimeToString(time) + "_";
   datetime endTime = time + PeriodSeconds() * 10; // خطوط کمی جلوتر بروند

   // Entry Line (Black Dot)
   CreateLine(prefix + "Entry", time, entry, endTime, entry, clrBlack);
   
   // SL Line (Red Dot)
   CreateLine(prefix + "SL", time, sl, endTime, sl, clrRed);

   // TP Line (Green Dot)
   CreateLine(prefix + "TP", time, tp, endTime, tp, clrGreen);
  }

void CreateLine(string name, datetime t1, double p1, datetime t2, double p2, color clr)
{
   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_STYLE, STYLE_DOT); // خط چین
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   }
}
//+------------------------------------------------------------------+
 
Mohammad Dehghani:
Suggest ways to filter out false signals in ranging conditions (e.g., using ATR, ADX, or price structure filters)?

Here's an MT5 indicator that must be converted for MT4 use. I posted it here merely to suggest a logical concept. The idea is to trade volatility when the average volume/tick volume is above the MA. Coded by Mladen Rakic:

EURUSDM1

This is a snapshot of a EURUSD M1 chart. I'm assuming that you're scalping based on your description of your strategy. To be clear, trading price spikes on a short timeframe with this indicator will put you in the news.

Files: