Indicator only works in Strategy tester (not live)

 

Morning,  The indicator does not show/render in the live environment. The indicator work perfectly in the Strategy tester.

I am using MT4 Version 4.00 build 1441

Windows 10

--------------

Screenshot shows the indicator with total tick volume and the delta for the day.

GBPUSD M5 chart. Screenshot of Tester

Any assistance would be appreciated. Thanks in advance.

//+------------------------------------------------------------------+
//|                                       Delta_Profile_Right.mq4    |
//|                                     Copyright © 2025, Bob        |
//| Simplified to start on attach, reset at 00:00 using iTime        |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2025, Bob"
#property link      ""
#property indicator_chart_window
#property strict

#property indicator_buffers 1
#property indicator_color1 clrGray
#property indicator_width1 1

//---- input parameters
input int     DeltaAmplitude = 40;
input bool    Show_DeltaProfile = true;
input double  PriceStep = 0.0001; // 1 pip for 5-digit brokers
input int     FontSize = 7;
input int     MaxTicks = 5000000;
input int     MaxPriceLevels = 200;

//---- buffers
double DeltaHist[];

//---- global variables
string OBJECT_PREFIX = "DeltaProfile_";
int items;
double minPrice, maxPrice;
datetime currentSessionStart = 0;
int lastProcessedTick = 0;

//---- data structures
struct TickStruct { datetime time; double bid; double ask; };
struct PriceLevel { double buyVolume; double sellVolume; double delta; double totalVolume; };
PriceLevel priceLevels[];
TickStruct g_ticks[];

void ObDeleteObjectsByPrefix(string Prefix) {
   int L = StringLen(Prefix);
   for (int i = ObjectsTotal() - 1; i >= 0; i--) {
      string ObjName = ObjectName(i);
      if (StringSubstr(ObjName, 0, L) == Prefix) ObjectDelete(ObjName);
   }
}

bool IsNewTradingDay(datetime tickTime) {
   if (currentSessionStart == 0) {
      currentSessionStart = tickTime;  // allow startup anytime
      return true;
   }
   datetime todayStart = iTime(NULL, PERIOD_D1, 0);
   return (tickTime >= todayStart && currentSessionStart < todayStart);
}


void ResetPriceLevels() {
   ArrayInitialize(DeltaHist, 0);
   ArrayResize(priceLevels, items);
   for (int i = 0; i < items; i++) {
      priceLevels[i].buyVolume = 0;
      priceLevels[i].sellVolume = 0;
      priceLevels[i].delta = 0;
      priceLevels[i].totalVolume = 0;
   }
}

void InitTicks() {
   ArrayResize(g_ticks, 0);
   lastProcessedTick = 0;
}

bool StoreTick(datetime time, double bid, double ask) {
   int total = ArraySize(g_ticks);
   if (total >= MaxTicks) {
      Print("Max tick limit reached: ", MaxTicks);
      return false;
   }
   if (ArrayResize(g_ticks, total + 1, 1000) < 0) {
      Print("Failed to resize g_ticks array");
      return false;
   }
   g_ticks[total].time = time;
   g_ticks[total].bid = bid;
   g_ticks[total].ask = ask;
   return true;
}

int FindDayStartIndex(const datetime &time[]) {
   datetime dayStart = iTime(NULL, PERIOD_D1, 0);
   int index = iBarShift(NULL, 0, dayStart, false);
   return index >= 0 ? index : Bars - 1;
}

int OnInit() {
   OBJECT_PREFIX = OBJECT_PREFIX + ChartID() + "_" + MathRand() + "_";
   IndicatorBuffers(1);
   SetIndexStyle(0, DRAW_NONE);
   SetIndexLabel(0, "DeltaHist");
   SetIndexBuffer(0, DeltaHist);
   IndicatorShortName("Delta_Profile");
   ObDeleteObjectsByPrefix("DeltaProfile_");
   ArrayResize(g_ticks, MaxTicks, 1000);
   ArrayResize(priceLevels, MaxPriceLevels, 100);
   ArrayResize(DeltaHist, MaxPriceLevels, 100);
   InitTicks();
   currentSessionStart = TimeCurrent();
   Print("Indicator initialized at: ", TimeToString(currentSessionStart, TIME_DATE|TIME_MINUTES));
   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 (TimeDayOfWeek(TimeCurrent()) == 0) {
      Print("Skipping Sunday");
      return rates_total;
   }

   double bid = MarketInfo(Symbol(), MODE_BID);
   double ask = MarketInfo(Symbol(), MODE_ASK);
   if (bid <= 0 || ask <= 0) {
      Print("Invalid bid/ask prices: Bid=", bid, ", Ask=", ask);
      return rates_total;
   }

   TickStruct tick;
   tick.time = TimeCurrent();
   tick.bid = bid;
   tick.ask = ask;

if (currentSessionStart == 0) {
   // First-time init: start NOW instead of midnight
   currentSessionStart = tick.time;
   InitTicks();
   lastProcessedTick = 0;
   ObDeleteObjectsByPrefix("DeltaProfile_");
   Print("First-time attach, starting at: ", TimeToString(currentSessionStart, TIME_DATE|TIME_MINUTES));
}
else if (IsNewTradingDay(tick.time)) {
   datetime dayStart = iTime(NULL, PERIOD_D1, 0);
   currentSessionStart = tick.time;

   InitTicks();
   lastProcessedTick = 0;
   ObDeleteObjectsByPrefix("DeltaProfile_");
   Print("New trading day started at: ", TimeToString(currentSessionStart, TIME_DATE|TIME_MINUTES));
}


   if (!StoreTick(tick.time, tick.bid, tick.ask)) {
      Print("Failed to store tick. Total ticks: ", ArraySize(g_ticks));
      return rates_total;
   }
   Print("Stored tick. Time: ", TimeToString(tick.time, TIME_MINUTES|TIME_SECONDS), ", Bid: ", tick.bid, ", Ask: ", tick.ask);

   int totalTicks = ArraySize(g_ticks);
   if (totalTicks <= lastProcessedTick || totalTicks < 2) {
      Print("Insufficient ticks: Total ticks: ", totalTicks, ", Last processed: ", lastProcessedTick);
      return rates_total;
   }

   int dayStartIndex = FindDayStartIndex(time);
   if (dayStartIndex >= rates_total) dayStartIndex = rates_total - 1;
   int barCount = rates_total - dayStartIndex;
   if (barCount <= 0) {
      Print("Invalid bar count: ", barCount);
      return rates_total;
   }

   maxPrice = High[iHighest(NULL, 0, MODE_HIGH, barCount, dayStartIndex)];
   minPrice = Low[iLowest(NULL, 0, MODE_LOW, barCount, dayStartIndex)];
   if (maxPrice - minPrice < PriceStep) {
      maxPrice = minPrice + PriceStep * 10; // Ensure at least 10 levels
      Print("Adjusted price range. MaxPrice: ", maxPrice, ", MinPrice: ", minPrice);
   }
   items = MathMin(MathRound((maxPrice - minPrice) / PriceStep), MaxPriceLevels);
   if (items <= 0) {
      Print("Invalid items count: ", items, ", MaxPrice: ", maxPrice, ", MinPrice: ", minPrice);
      return rates_total;
   }

   ArrayResize(DeltaHist, items, 50);
   ArrayResize(priceLevels, items, 50);
   ResetPriceLevels();

   for (int i = 1; i < totalTicks; i++) {
      datetime t = g_ticks[i].time;
      double pricePrev = g_ticks[i - 1].bid;
      double priceCurr = g_ticks[i].bid;
      int buyVolume = 0, sellVolume = 0;
      if (priceCurr > pricePrev) {
         buyVolume++;
      } else if (priceCurr < pricePrev) {
         sellVolume++;
      } else {
         int barIndex = iBarShift(NULL, 0, t, true);
         if (barIndex >= 0 && barIndex < rates_total) {
            buyVolume += tick_volume[barIndex] / 2;
            sellVolume += tick_volume[barIndex] / 2;
         } else {
            buyVolume++;
            sellVolume++;
         }
      }

      double price = NormalizeDouble(priceCurr, Digits);
      int n = MathRound((price - minPrice) / PriceStep);
      if (n >= 0 && n < items) {
         priceLevels[n].buyVolume += buyVolume;
         priceLevels[n].sellVolume += sellVolume;
         priceLevels[n].delta = priceLevels[n].buyVolume - priceLevels[n].sellVolume;
         priceLevels[n].totalVolume = priceLevels[n].buyVolume + priceLevels[n].sellVolume;
         DeltaHist[n] = priceLevels[n].totalVolume;
      }
   }
   lastProcessedTick = totalTicks;

   ObDeleteObjectsByPrefix("DeltaProfile_");
   if (Show_DeltaProfile) {
      double maxVolume = DeltaHist[ArrayMaximum(DeltaHist)];
      if (maxVolume == 0) {
         Print("Max volume is zero. Forcing dummy volume to test rendering");
         maxVolume = 1;
         for (int i = 0; i < items; i++) {
            priceLevels[i].buyVolume = 1; // Dummy volume
            priceLevels[i].totalVolume = 1;
            DeltaHist[i] = priceLevels[i].totalVolume;
         }
      }
      datetime rightEdgeTime = time[0] + PeriodSeconds() * 68;
      if (ChartGetInteger(0, CHART_SHIFT)) rightEdgeTime += PeriodSeconds() * 5;

      double pip = (Digits == 5 || Digits == 3) ? 0.00010 : 0.01;
      double heightPerBar = pip * 0.8;

      for (int i = 0; i < items; i++) {
         double buyVol = priceLevels[i].buyVolume;
         double sellVol = priceLevels[i].sellVolume;
         double totalVol = buyVol + sellVol;
         if (totalVol < 1.0) continue;

         double priceLevel = minPrice + i * PriceStep;
         double y1 = priceLevel;
         double y2 = priceLevel + heightPerBar;

         double buyRatio = buyVol / maxVolume;
         double sellRatio = sellVol / maxVolume;

         int buyWidth = MathMax(MathRound(buyRatio * DeltaAmplitude), 1);
         int sellWidth = MathMax(MathRound(sellRatio * DeltaAmplitude), 1);

         datetime xStart = rightEdgeTime;
         datetime xBuyEnd = xStart - buyWidth * PeriodSeconds();
         datetime xSellEnd = xBuyEnd - sellWidth * PeriodSeconds();

         string buyRect = OBJECT_PREFIX + "BuyRect_" + i;
         if (ObjectFind(0, buyRect) < 0) {
            ObjectCreate(0, buyRect, OBJ_RECTANGLE, 0, xStart, y1, xBuyEnd, y2);
            ObjectSetInteger(0, buyRect, OBJPROP_COLOR, clrSeaGreen);
            ObjectSetInteger(0, buyRect, OBJPROP_BACK, true);
            ObjectSetInteger(0, buyRect, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, buyRect, OBJPROP_HIDDEN, false);
         } else {
            ObjectSetDouble(0, buyRect, OBJPROP_PRICE1, y1);
            ObjectSetDouble(0, buyRect, OBJPROP_PRICE2, y2);
            ObjectSetInteger(0, buyRect, OBJPROP_TIME1, xStart);
            ObjectSetInteger(0, buyRect, OBJPROP_TIME2, xBuyEnd);
         }

         string sellRect = OBJECT_PREFIX + "SellRect_" + i;
         if (ObjectFind(0, sellRect) < 0) {
            ObjectCreate(0, sellRect, OBJ_RECTANGLE, 0, xBuyEnd, y1, xSellEnd, y2);
            ObjectSetInteger(0, sellRect, OBJPROP_COLOR, C'120,14,0');
            ObjectSetInteger(0, sellRect, OBJPROP_BACK, true);
            ObjectSetInteger(0, sellRect, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, sellRect, OBJPROP_HIDDEN, false);
         } else {
            ObjectSetDouble(0, sellRect, OBJPROP_PRICE1, y1);
            ObjectSetDouble(0, sellRect, OBJPROP_PRICE2, y2);
            ObjectSetInteger(0, sellRect, OBJPROP_TIME1, xBuyEnd);
            ObjectSetInteger(0, sellRect, OBJPROP_TIME2, xSellEnd);
         }

         int delta = MathAbs((int)(buyVol - sellVol));
         string deltaStr = (delta >= 1000) ? StringFormat("%.1fk", delta / 1000.0) : IntegerToString(delta);
         string totalStr = (totalVol >= 1000) ? StringFormat("%.1fk", totalVol / 1000.0) : IntegerToString((int)totalVol);

         string txt = OBJECT_PREFIX + "VolText_" + i;
         datetime deltaX = xSellEnd - 1 * PeriodSeconds();
         if (ObjectFind(0, txt) < 0) {
            ObjectCreate(0, txt, OBJ_TEXT, 0, deltaX, y1 + 0.00003);
            ObjectSetInteger(0, txt, OBJPROP_ANCHOR, ANCHOR_RIGHT);
            ObjectSetInteger(0, txt, OBJPROP_HIDDEN, false);
         }
         ObjectSetText(txt, deltaStr, 8, "Arial Bold", buyVol >= sellVol ? clrLimeGreen : clrRed);
         ObjectSetInteger(0, txt, OBJPROP_COLOR, buyVol >= sellVol ? clrLimeGreen : clrRed);

         string totalTxt = OBJECT_PREFIX + "TotalText_" + i;
         if (ObjectFind(0, totalTxt) < 0) {
            ObjectCreate(0, totalTxt, OBJ_TEXT, 0, deltaX - 6 * PeriodSeconds(), y1 + 0.00003);
            ObjectSetInteger(0, totalTxt, OBJPROP_ANCHOR, ANCHOR_RIGHT);
            ObjectSetInteger(0, totalTxt, OBJPROP_HIDDEN, false);
         }
         ObjectSetText(totalTxt, totalStr, FontSize, "Verdana", clrSilver);
         ObjectSetInteger(0, totalTxt, OBJPROP_COLOR, clrSilver);
      }
      Print("Created/Updated ", items, " price level objects");
   }

   Print("Processed ", totalTicks, " ticks. Items: ", items, ", MaxPrice: ", maxPrice, ", MinPrice: ", minPrice);
   return rates_total;
}

int deinit() {
   ObDeleteObjectsByPrefix("DeltaProfile_");
   Print("Indicator deinitialized");
   return 0;
}
 
Robin Jones:
Any assistance would be appreciated. Thanks in advance.

I'm not 100% sure if this affects MT4, but best practice is to put your custom functions after the OnCalculate() return statement--instead of frontloading them ahead of OnInit().

 
Ryan L Johnson #:

I'm not 100% sure if this affects MT4, but best practice is to put your custom functions after the OnCalculate() return statement--instead of frontloading them ahead of OnInit().

Thanks Ryan... but no change. I think its a tick issue.

 
Robin Jones #:

Thanks Ryan... but no change. I think its a tick issue.

In that case, using a regular indicator loop that references rates_total inside OnCalculate() will likely get the ticks flowing.

 
Ryan L Johnson #:

In that case, using a regular indicator loop that references rates_total inside OnCalculate() will likely get the ticks flowing.

Not quite sure how to do that.

 
Robin Jones #:

Not quite sure how to do that.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate( const int rates_total,    // total number of bars on the current tick
               const int prev_calculated,// number of calculated bars on the previous tick
               const datetime& time[],
               const double& open[],    
               const double& high[],     // price array for the maximum price for the indicator calculation
               const double& low[],      // price array for the minimum price for the indicator calculation
               const double& close[],
               const long& tick_volume[],
               const long& volume[],
               const int& spread[]
             )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
   
   //--- declare the local variables 
   int first, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar number
   if (prev_calculated == 0)  // checking for the first start of the indicator calculation
     first = AroonPeriod - 1; // starting number for the calculation of all of the bars

   else first = prev_calculated - 1; // starting number for the calculation of new bars

   //--- main loop
   for(bar = first; bar < rates_total; bar++)
    {
     //--- calculation of values
     BULLS = 100 - (bar - iHighest(high, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;
     BEARS = 100 - (bar - iLowest (low,  AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//---+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

Articles

Creating an Indicator with Multiple Indicator Buffers for Newbies

Nikolay Kositsin, 2010.12.08 10:53

The complex codes consist of a set of simple codes. If you are familiar with them, it doesn't look so complicated. In this article, we will consider how to create an indicator with multiple indicator buffers. As an example, the Aroon indicator is analyzed in details, and two different versions of the code are presented.

 
Thanks for the help, but I am really out of my depth here. I would not know how to insert that into my code.
 
Robin Jones #:
Thanks for the help, but I am really out of my depth here. I would not know how to insert that into my code.

If I'm not mistaken, your code has 1 invisible indicator buffer and draws objects in lieu thereof. At this point I have to say... rewrite the whole thing into an Expert Advisor which uses OnTick() instead of OnCalculate(). It would largely be copy & paste. This is not unusual. There are plenty of object-drawing EA's out there.

 
Ryan L Johnson #:

If I'm not mistaken, your code has 1 invisible indicator buffer and draws objects in lieu thereof. At this point I have to say... rewrite the whole thing into an Expert Advisor which uses OnTick() instead of OnCalculate(). It would largely be copy & paste. This is not unusual. There are plenty of object-drawing EA's out there.

Thanks, I will give that a try, not sure if it will render the rectangle bars though.