Please avoid writing topic titles in all caps in the future. (It has been corrected this time.)
Where is your
WasAlreadyAlerted
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.
//+------------------------------------------------------------------+ //| 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]);
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?
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?
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?

- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
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.