Rectangle objects wont follow price to the last closed candle as price keeps moving

 

Hey guys hope everyone is doing well. 

I got a piece of code here trying to create rectangles to cover FVG zones drawiing from the zone itself to the last closed price. As price is moving printing new candles the box is left behind where it loaded. Can anyone assist draw the box to follow price on each last closed candle and not just once and it stops. here is the Oncalculate code with the drawing and alerting logic. I just need help with the drawing part to keep the rectangle objects move with price.


   // IFVG CREATION LOGIC
   
int lastClosedBar = rates_total - 2;

for (int m = 0; m < mergedCount; m++) {
   FVG fvg = merged[m];
   int startBar = fvg.endBar - 1;

   double zoneTop = MathMax(fvg.highVal, fvg.lowVal);
   double zoneBottom = MathMin(fvg.highVal, fvg.lowVal);

   for (int i = startBar; i <= lastClosedBar; i++) {  // ✅ Only use closed bars
      bool isBullish = fvg.type == -1;
      bool isBearish = fvg.type == 1;

      bool condition = (isBullish && close[i] > zoneTop) || (isBearish && close[i] < zoneBottom);
      if (!condition) continue;

      string name = (isBullish ? "iFVG_bull_" : "iFVG_bear_") + IntegerToString(time[i]);
      
      // 🚫 Skip drawing if this iFVG was already invalidated
      if (WasAlreadyAlerted(name, alertedInvalidations)) continue;

         if (!ObjectExistsInArray(name, currentObjects)) {
  
         // ✅ Draw from start bar to current (live) time so box follows price
         ObjectCreate(0, name, OBJ_RECTANGLE, 0, time[startBar], zoneBottom, time[rates_total - 2], zoneTop);
         ObjectSetInteger(0, name, OBJPROP_COLOR, isBullish ? iBullishColor : iBearishColor);
         ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, name, OBJPROP_BACK, true);

         // ✅ Alert only for zones formed at the last closed candle
         if (EnableiFVGFormationAlert && i == lastClosedBar) {
            string tf = GetReadablePeriod();
            string msg = StringFormat("[%s] [%s] %s iFVG formed at %.5f on %s",
               _Symbol, tf,
               isBullish ? "Bullish" : "Bearish",
               close[i],
               TimeToString(time[i], TIME_DATE | TIME_MINUTES));

            Alert(msg);
            SendNotification(msg);
         }
      }

      break;  // ✅ Only process once per FVG
   }
}


// ✅ Then check invalidations only after drawing
CleanupiFVGs(time, close, rates_total, prev_calculated);
return rates_total;
}
 
Ismail Hassan Lemin:

Please avoid writing topic titles in all caps in the future. (It has been corrected this time.)

 
Miguel Angel Vico Alba #:

Please avoid writing topic titles in all caps in the future. (It has been corrected this time.)

Copy that won't do that again. And about tge code can you please help with a solution to that? 
 

Where is your

WasAlreadyAlerted

custom function?

 
Ryan L Johnson #:

Where is your

custom function?

//helper Function too
bool WasAlreadyAlerted(const string &name, const string &list[])
{
   for (int i = 0; i < ArraySize(list); i++) {
      if (list[i] == name)
         return true;
   }
   return false;
}

its outside the oncalculate as a helper function and also the cleanup logic is outside too just called once at the end of the main. Not sure if i can share the full code or it will be limited becasue of size.

 
Ryan L Johnson #:

Where is your

custom function?

//+------------------------------------------------------------------+
//|  IFVG INDICATOR                    |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property strict
#property indicator_buffers 0
#property indicator_plots   0

input int    LookbackBars       = 100;
input int    ATR_Period         = 14;
input double ATR_Multiplier     = 0.3;
input bool   EnableMerging      = false;
input int    MinFVGsToMerge     = 2;
input int    MaxFVGsToMerge     = 5;

input bool EnableiFVGFormationAlert = true;
input bool EnableiFVGInvalidationAlert = true;
input color  BullishColor       = clrGreen;
input color  BearishColor       = clrRed;
input color  iBullishColor      = clrForestGreen;
input color  iBearishColor      = clrCrimson;
input int    ArrowCodeBullish   = 233;
input int    ArrowCodeBearish   = 234;
input int    ArrowSize          = 1;
input int    ArrowOffsetPoints  = 20;
input bool   ShowFVGArrows      = false;



//Globals

string alertedInvalidations[];  // Move this outside any function – global



struct FVG {
   int type;
   int startBar;
   int endBar;
   double highVal;
   double lowVal;
   double atr;
};

// ✅ Timeframe label formatter
string GetReadablePeriod() {
   switch(_Period) {
      case PERIOD_M1:   return "M1";
      case PERIOD_M5:   return "M5";
      case PERIOD_M15:  return "M15";
      case PERIOD_M20:  return "M20";
      case PERIOD_M30:  return "M30";
      case PERIOD_H1:   return "H1";
      case PERIOD_H4:   return "H4";
      case PERIOD_H6:   return "H6";
      case PERIOD_H8:   return "H8";
      case PERIOD_H12:   return "H12";
      case PERIOD_D1:   return "D1";
      case PERIOD_W1:   return "W1";
      case PERIOD_MN1:  return "MN1";
      default:          return EnumToString(_Period);
   }
}

double CalculateATR(const double &high[], const double &low[], const double &close[], int current) {
   if (current < ATR_Period) return 0.0;
   double sum = 0;
   for (int i = current - ATR_Period + 1; i <= current; i++) {
      double tr = MathMax(high[i] - low[i], MathMax(MathAbs(high[i] - close[i - 1]), MathAbs(low[i] - close[i - 1])));
      sum += tr;
   }
   return sum / ATR_Period;
}

void OnDeinit(const int reason) {
   int total = ObjectsTotal(0);
   for (int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if (StringFind(name, "BullFVG_") == 0 || StringFind(name, "BearFVG_") == 0 || StringFind(name, "iFVG_") == 0)
         ObjectDelete(0, name);
   }
}

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 < ATR_Period + 3)
      return prev_calculated;

   static int lastProcessedBar = 0;
   if (rates_total <= lastProcessedBar)
      return prev_calculated;
   lastProcessedBar = rates_total;
   
   
   // Then use this array for quick lookup or checks instead of ObjectFind calls repeatedly
   string currentObjects[];
int totalObjects = ObjectsTotal(0);
ArrayResize(currentObjects, totalObjects);
for (int i = 0; i < totalObjects; i++) {
    currentObjects[i] = ObjectName(0, i);
}


   static datetime lastDeleteTime = 0;
   datetime currentBarTime = time[rates_total - 1];
   if (currentBarTime != lastDeleteTime) {
      int total = ObjectsTotal(0);
      for (int j = total - 1; j >= 0; j--) {
         string name = ObjectName(0, j);
         if (StringFind(name, "BullFVG_") == 0 || StringFind(name, "BearFVG_") == 0)
            ObjectDelete(0, name);
      }
      lastDeleteTime = currentBarTime;
   }

   // ✅ Precompute ATR values
   static double atrValues[];
   if (ArraySize(atrValues) < rates_total)
      ArrayResize(atrValues, rates_total);

   int startATR = MathMax(ATR_Period, prev_calculated - 1);
   for (int i = startATR; i < rates_total; i++)
      atrValues[i] = CalculateATR(high, low, close, i) * ATR_Multiplier;

   int start = MathMax(ATR_Period, rates_total - LookbackBars - 3);
   FVG fvgs[];
   int fvgCount = 0;

   for (int i = start; i < rates_total - 3; i++) {
      double atr = atrValues[i];
      if (low[i + 2] > high[i] && (low[i + 2] - high[i]) >= atr) {
         FVG fvg = {1, i, i + 2, high[i], low[i + 2], atr};
         ArrayResize(fvgs, fvgCount + 1);
         fvgs[fvgCount++] = fvg;
      } else if (high[i + 2] < low[i] && (low[i] - high[i + 2]) >= atr) {
         FVG fvg = {-1, i, i + 2, high[i + 2], low[i], atr};
         ArrayResize(fvgs, fvgCount + 1);
         fvgs[fvgCount++] = fvg;
      }
   }

 

   FVG merged[];
   int mergedCount = 0;
   for (int j = 0; j < fvgCount; j++) {
      if (!EnableMerging || j + MinFVGsToMerge - 1 >= fvgCount) {
         ArrayResize(merged, mergedCount + 1);
         merged[mergedCount++] = fvgs[j];
         continue;
      }

      int bestGroup = 1;
      for (int g = MaxFVGsToMerge; g >= MinFVGsToMerge; g--) {
         if (j + g - 1 >= fvgCount) continue;
         bool valid = true;
         for (int k = 1; k < g; k++) {
            if (fvgs[j + k].type != fvgs[j].type || fvgs[j + k].startBar != fvgs[j + k - 1].startBar + 1) {
               valid = false;
               break;
            }
         }

         if (!valid) continue;

         double top = fvgs[j].highVal;
         double bottom = fvgs[j].lowVal;
         for (int k = 1; k < g; k++) {
            top = (fvgs[j].type == 1) ? MathMax(top, fvgs[j + k].highVal) : MathMin(top, fvgs[j + k].highVal);
            bottom = (fvgs[j].type == 1) ? MathMin(bottom, fvgs[j + k].lowVal) : MathMax(bottom, fvgs[j + k].lowVal);
         }

         double size = MathAbs(bottom - top);
         if (size >= fvgs[j].atr) {
            FVG mergedFVG = {fvgs[j].type, fvgs[j].startBar, fvgs[j + g - 1].endBar, top, bottom, fvgs[j].atr};
            ArrayResize(merged, mergedCount + 1);
            merged[mergedCount++] = mergedFVG;
            j += g - 1;
            bestGroup = g;
            break;
         }
      }

      if (bestGroup == 1) {
         ArrayResize(merged, mergedCount + 1);
         merged[mergedCount++] = fvgs[j];
      }
   }

   if (ShowFVGArrows) {
   for (int m = 0; m < mergedCount; m++) {
      FVG fvg = merged[m];
      
      // ⚠️ Generate the would-be iFVG name (to detect invalidation)
      string iFVG_Name = (fvg.type == -1 ? "iFVG_bull_" : "iFVG_bear_") + IntegerToString(time[fvg.endBar - 1]);

      // ❌ Skip drawing arrow if the FVG was invalidated (i.e. iFVG formed from it)
    if (ObjectExistsInArray(iFVG_Name, currentObjects)) {
   // 🔥 Try to delete the corresponding arrow object (if it exists)
   string oldArrowName = (fvg.type == 1 ? "BullFVG_" : "BearFVG_") + IntegerToString(fvg.startBar);
   ObjectDelete(0, oldArrowName);
   continue;
}

      string name = (fvg.type == 1 ? "BullFVG_" : "BearFVG_") + IntegerToString(fvg.startBar);
      double price = (fvg.type == 1) ? (fvg.lowVal - ArrowOffsetPoints * _Point) : (fvg.highVal + ArrowOffsetPoints * _Point);

      if (ObjectCreate(0, name, OBJ_ARROW, 0, time[fvg.endBar], price)) {
         ObjectSetInteger(0, name, OBJPROP_ARROWCODE, fvg.type == 1 ? ArrowCodeBullish : ArrowCodeBearish);
         ObjectSetInteger(0, name, OBJPROP_COLOR, fvg.type == 1 ? BullishColor : BearishColor);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, ArrowSize);
      }
   }
}

   
   // IFVG CREATION LOGIC
   
int lastClosedBar = rates_total - 2;

for (int m = 0; m < mergedCount; m++) {
   FVG fvg = merged[m];
   int startBar = fvg.endBar - 1;

   double zoneTop = MathMax(fvg.highVal, fvg.lowVal);
   double zoneBottom = MathMin(fvg.highVal, fvg.lowVal);

   for (int i = startBar; i <= lastClosedBar; i++) {  // ✅ Only use closed bars
      bool isBullish = fvg.type == -1;
      bool isBearish = fvg.type == 1;

      bool condition = (isBullish && close[i] > zoneTop) || (isBearish && close[i] < zoneBottom);
      if (!condition) continue;

      string name = (isBullish ? "iFVG_bull_" : "iFVG_bear_") + IntegerToString(time[i]);
      
      // 🚫 Skip drawing if this iFVG was already invalidated
      if (WasAlreadyAlerted(name, alertedInvalidations)) continue;

         if (!ObjectExistsInArray(name, currentObjects)) {
  
         // ✅ Draw from start bar to current (live) time so box follows price
         ObjectCreate(0, name, OBJ_RECTANGLE, 0, time[startBar], zoneBottom, time[rates_total - 2], zoneTop);
         ObjectSetInteger(0, name, OBJPROP_COLOR, isBullish ? iBullishColor : iBearishColor);
         ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
         ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, name, OBJPROP_BACK, true);

         // ✅ Alert only for zones formed at the last closed candle
         if (EnableiFVGFormationAlert && i == lastClosedBar) {
            string tf = GetReadablePeriod();
            string msg = StringFormat("[%s] [%s] %s iFVG formed at %.5f on %s",
               _Symbol, tf,
               isBullish ? "Bullish" : "Bearish",
               close[i],
               TimeToString(time[i], TIME_DATE | TIME_MINUTES));

            Alert(msg);
            SendNotification(msg);
         }
      }

      break;  // ✅ Only process once per FVG
   }
}
  
  

// ✅ Then check invalidations only after drawing
CleanupiFVGs(time, close, rates_total, prev_calculated);
return rates_total;
}


//helper Function too
bool WasAlreadyAlerted(const string &name, const string &list[])
{
   for (int i = 0; i < ArraySize(list); i++) {
      if (list[i] == name)
         return true;
   }
   return false;
}


bool ObjectExistsInArray(const string &name, const string &arr[]) {
    int size = ArraySize(arr);
    for (int i = 0; i < size; i++) {
        if (arr[i] == name)
            return true;
    }
    return false;
}

//Cleanup Logic
void CleanupiFVGs(const datetime &time[], const double &close[], int rates_total, const int prev_calculated) {

   int total = ObjectsTotal(0);
   for (int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if (StringFind(name, "iFVG_bull_") == 0 || StringFind(name, "iFVG_bear_") == 0) {
         string parts[];
         if (StringSplit(name, '_', parts) < 3) continue;
         datetime t = (datetime)StringToInteger(parts[2]);

         int index = -1;
         for (int j = 0; j < rates_total; j++) {
            if (time[j] == t) {
               index = j;
               break;
            }
         }
         if (index == -1) continue;

         double price1 = ObjectGetDouble(0, name, OBJPROP_PRICE, 0);
         double price2 = ObjectGetDouble(0, name, OBJPROP_PRICE, 1);
         double zoneTop = MathMax(price1, price2);
         double zoneBottom = MathMin(price1, price2);

         int lastClosedBar = rates_total - 2;

         for (int j = index + 1; j <= lastClosedBar; j++) {
            bool isBullish = StringFind(name, "iFVG_bull_") == 0;
            bool isBearish = StringFind(name, "iFVG_bear_") == 0;

            bool condition = (isBullish && close[j] < zoneBottom) || (isBearish && close[j] > zoneTop);
            if (!condition) continue;

            bool notAlreadyAlerted = !WasAlreadyAlerted(name, alertedInvalidations);

            // ✅ Only alert if this is NOT the initial load (prev_calculated > 0)
            // ✅ And the break is happening on the latest closed candle
            if (EnableiFVGInvalidationAlert && notAlreadyAlerted && prev_calculated > 0 && j == lastClosedBar) {
               string tf = GetReadablePeriod();
               string msg = StringFormat("[%s] [%s] %s iFVG Broken at %.5f on %s",
                                         _Symbol, tf, isBullish ? "Bullish" : "Bearish",
                                         close[j], TimeToString(time[j], TIME_DATE | TIME_MINUTES));
               Alert(msg);
               SendNotification(msg);

               int n = ArraySize(alertedInvalidations);
               ArrayResize(alertedInvalidations, n + 1);
               alertedInvalidations[n] = name;
            }

            // 🔇 Delete the object in all cases to keep the chart clean
            ObjectDelete(0, name);
            break;
         }
      }
   }
   // Limit alertedInvalidations array size to max 100 entries to avoid memory bloat
int maxAlertsStored = 50;
if (ArraySize(alertedInvalidations) > maxAlertsStored) {
    int excess = ArraySize(alertedInvalidations) - maxAlertsStored;
    for (int i = 0; i < ArraySize(alertedInvalidations) - excess; i++) {
        alertedInvalidations[i] = alertedInvalidations[i + excess];
    }
    ArrayResize(alertedInvalidations, maxAlertsStored);
}

}
 

Try replacing IntegerToString() with TimeToString() in

      string name = (isBullish ? "iFVG_bull_" : "iFVG_bear_") + IntegerToString(time[i]);
 
Ryan L Johnson #:

Try replacing IntegerToString() with TimeToString() in

That causes the old invalidated levels within the lookback to reappear on the chart 
 
Ismail Hassan Lemin #:
That causes the old invalidated levels within the lookback to reappear on the chart 

Strange. And with no effect on updating rectangles, presumably.

I see that lastClosedBar is related to rates_total in its definition, but I don't see where startBar is related to rates_total. How does startBar "tick" along?

 
Ryan L Johnson #:

Strange. And with no effect on updating rectangles, presumably.

I see that lastClosedBar is related to rates_total in its definition, but I don't see where startBar is related to rates_total. How does startBar "tick" along?

Start bar is fine all valid rectangle start at the original FVG gap but the issue is the end time. 
 
Ryan L Johnson #:

Strange. And with no effect on updating rectangles, presumably.

I see that lastClosedBar is related to rates_total in its definition, but I don't see where startBar is related to rates_total. How does startBar "tick" along?

I also find it strange. Everything seems perfect and the code looks solid from drawings to the supporting function but for some reason, the boxes won't move with price.