City Times


London

Paris

New York

Tokyo

Sydney

FBS Top

Google Search

Saturday, May 10, 2025

My run at coding an mt5 Indicator for Open range




    The primary issues I noticed are:

Missing variables for anti-cluttering (MinBarsBetweenLabels, MinPriceDistanceFactor)
Multiple function redefinitions
Issues with TimeToStruct function parameters
Undeclared identifiers ('day', 'day_of_week', 'mon')

below is the code


------------------------------------------------------------------------------------------------------------________________________________________________________________________
#property copyright "Copyright 2025, MT5 Indicator"
#property link      ""
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

// Enum for session types
enum ENUM_SESSION_TYPE {
   SESSION_LONDON,   // London
   SESSION_NEWYORK,  // New York
   SESSION_SYDNEY,   // Sydney
   SESSION_ASIA      // Asia
};

// Input parameters
input bool           ShowSessionLabels = true;  // Show Session Labels
input bool           ShowDayLabels = true;      // Show Day Labels
input bool           ShowWeekLabels = true;     // Show Week Labels
input bool           ShowMonthLabels = true;    // Show Month Labels
input bool           ShowYearLabels = true;     // Show Year Labels
input string         LabelFont = "Arial";       // Label Font
input int            LabelFontSize = 8;         // Label Font Size
input color          PositiveColor = clrGreen;  // Positive Label Color
input color          NegativeColor = clrRed;    // Negative Label Color
input color          OpenRangeColor = clrBlue;  // Open Range Label Color
input int            LabelHorizontalOffset = 5; // Label Horizontal Offset (pixels)
input int            LabelVerticalSpacing = 15; // Label Vertical Spacing (pixels)

// Session time inputs
input string         SessionSeparator = "------- Session Times -------";
input int            LondonOpenHour = 8;        // London Open Hour (GMT)
input int            LondonCloseHour = 16;      // London Close Hour (GMT)
input int            NewYorkOpenHour = 13;      // New York Open Hour (GMT)
input int            NewYorkCloseHour = 21;     // New York Close Hour (GMT)
input int            SydneyOpenHour = 22;       // Sydney Open Hour (GMT)
input int            SydneyCloseHour = 6;       // Sydney Close Hour (GMT)
input int            AsiaOpenHour = 0;          // Asia Open Hour (GMT)
input int            AsiaCloseHour = 9;         // Asia Close Hour (GMT)

// Structure to store wave points
struct WavePoint {
   datetime time;
   double   price;
   string   label;
   bool     isPositive;
   int      type;        // 0=target, 1=flip, 2=high/low
   string   objectName;  // To store the name of the created object for reference
};

// Point tracking information for anti-cluttering logic
struct PointTracker {
   datetime lastLabelTime;
   double   lastLabelPrice;
   int      lastLabelDirection;  // 1 for above price, -1 for below price
   int      verticalCounter;     // Used to stack labels vertically when needed
};

// Arrays to store wave points for different timeframes
WavePoint sessionPoints[];
WavePoint dayPoints[];
WavePoint weekPoints[];
WavePoint monthPoints[];
WavePoint yearPoints[];

// Point trackers for different timeframes
PointTracker sessionTracker;
PointTracker dayTracker;
PointTracker weekTracker;
PointTracker monthTracker;
PointTracker yearTracker;

// Session tracking variables
struct TimeframeData {
   datetime openRangeTime;
   double   openRangeHigh;
   double   openRangeLow;
   bool     directionPositive;
   int      state;       // 0=initial, 1=target, 2=flip, 3=high/low
   datetime lastExtremumTime;
   double   lowestLowSinceOpenRange;
   double   highestHighSinceOpenRange;
   datetime lowestLowTime;
   datetime highestHighTime;
};

// Timeframe data structures
TimeframeData sessionData;
TimeframeData dayData;
TimeframeData weekData;
TimeframeData monthData;
TimeframeData yearData;

// Current session tracker
ENUM_SESSION_TYPE currentSession = SESSION_LONDON;
datetime lastSessionChangeTime = 0;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit() {
   // Set indicator properties
   IndicatorSetString(INDICATOR_SHORTNAME, "Open Range Wave Fractals");
   
   // Initialize timeframe data
   InitTimeframeData(sessionData);
   InitTimeframeData(dayData);
   InitTimeframeData(weekData);
   InitTimeframeData(monthData);
   InitTimeframeData(yearData);
   
   // Initialize point trackers
   InitPointTracker(sessionTracker);
   InitPointTracker(dayTracker);
   InitPointTracker(weekTracker);
   InitPointTracker(monthTracker);
   InitPointTracker(yearTracker);
   
   // Resize arrays
   ArrayResize(sessionPoints, 0);
   ArrayResize(dayPoints, 0);
   ArrayResize(weekPoints, 0);
   ArrayResize(monthPoints, 0);
   ArrayResize(yearPoints, 0);
   
   // Clean up any existing objects
   CleanupObjects();
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Initialize timeframe data structure                              |
//+------------------------------------------------------------------+
void InitTimeframeData(TimeframeData &data) {
   data.openRangeTime = 0;
   data.openRangeHigh = 0;
   data.openRangeLow = 0;
   data.directionPositive = false;
   data.state = 0;
   data.lastExtremumTime = 0;
   data.lowestLowSinceOpenRange = 0;
   data.highestHighSinceOpenRange = 0;
   data.lowestLowTime = 0;
   data.highestHighTime = 0;
}

//+------------------------------------------------------------------+
//| Initialize point tracker structure                               |
//+------------------------------------------------------------------+
void InitPointTracker(PointTracker &tracker) {
   tracker.lastLabelTime = 0;
   tracker.lastLabelPrice = 0;
   tracker.lastLabelDirection = 0;
   tracker.verticalCounter = 0;
}

//+------------------------------------------------------------------+
//| 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[]) {
   // Calculate only new bars
   int start = (prev_calculated > 0) ? prev_calculated - 1 : 0;
   
   // Ensure we have at least 2 bars to analyze
   if(rates_total < 2) return(rates_total);
   
   // Loop through bars
   for(int i = start; i < rates_total; i++) {
      // Skip the first bar in the calculation
      if(i == 0) continue;
      
      // Get current and previous time structures
      MqlDateTime dt_prev, dt_curr;
      TimeToStruct(time[i-1], dt_prev);
      TimeToStruct(time[i], dt_curr);
      
      // Check for new day
      bool isNewDay = dt_prev.day != dt_curr.day;
      if(isNewDay && ShowDayLabels) {
         // New day detected
         dayData.openRangeTime = time[i-1];
         dayData.openRangeHigh = high[i-1];
         dayData.openRangeLow = low[i-1];
         dayData.directionPositive = false;
         dayData.state = 0;
         dayData.lowestLowSinceOpenRange = low[i];
         dayData.highestHighSinceOpenRange = high[i];
         dayData.lowestLowTime = time[i];
         dayData.highestHighTime = time[i];
         
         // Create day open range labels with anti-cluttering
         CreateOpenRangeLabels("DayOpenRange", time[i], dayData.openRangeHigh, dayData.openRangeLow, "ORH", "ORL", dayTracker);
      }
      
      // Check for new week
      bool isNewWeek = dt_prev.day_of_week > dt_curr.day_of_week;
      if(isNewWeek && ShowWeekLabels) {
         // New week detected
         weekData.openRangeTime = time[i-1];
         weekData.openRangeHigh = high[i-1];
         weekData.openRangeLow = low[i-1];
         weekData.directionPositive = false;
         weekData.state = 0;
         weekData.lowestLowSinceOpenRange = low[i];
         weekData.highestHighSinceOpenRange = high[i];
         weekData.lowestLowTime = time[i];
         weekData.highestHighTime = time[i];
         
         // Create week open range labels with anti-cluttering
         CreateOpenRangeLabels("WeekOpenRange", time[i], weekData.openRangeHigh, weekData.openRangeLow, "WK orH", "WK orL", weekTracker);
      }
      
      // Check for new month
      bool isNewMonth = dt_prev.mon != dt_curr.mon;
      if(isNewMonth && ShowMonthLabels) {
         // New month detected
         monthData.openRangeTime = time[i-1];
         monthData.openRangeHigh = high[i-1];
         monthData.openRangeLow = low[i-1];
         monthData.directionPositive = false;
         monthData.state = 0;
         monthData.lowestLowSinceOpenRange = low[i];
         monthData.highestHighSinceOpenRange = high[i];
         monthData.lowestLowTime = time[i];
         monthData.highestHighTime = time[i];
         
         // Create month open range labels with anti-cluttering
         CreateOpenRangeLabels("MonthOpenRange", time[i], monthData.openRangeHigh, monthData.openRangeLow, "M orH", "M orL", monthTracker);
      }
      
      // Check for new year
      bool isNewYear = dt_prev.year != dt_curr.year;
      if(isNewYear && ShowYearLabels) {
         // New year detected
         yearData.openRangeTime = time[i-1];
         yearData.openRangeHigh = high[i-1];
         yearData.openRangeLow = low[i-1];
         yearData.directionPositive = false;
         yearData.state = 0;
         yearData.lowestLowSinceOpenRange = low[i];
         yearData.highestHighSinceOpenRange = high[i];
         yearData.lowestLowTime = time[i];
         yearData.highestHighTime = time[i];
         
         // Create year open range labels with anti-cluttering
         CreateOpenRangeLabels("YearOpenRange", time[i], yearData.openRangeHigh, yearData.openRangeLow, "Y orH", "Y orL", yearTracker);
      }
      
      // Check for session change
      ENUM_SESSION_TYPE newSession = DetermineCurrentSession(time[i]);
      if(newSession != currentSession && ShowSessionLabels) {
         // New session detected
         sessionData.openRangeTime = time[i-1];
         sessionData.openRangeHigh = high[i-1];
         sessionData.openRangeLow = low[i-1];
         sessionData.directionPositive = false;
         sessionData.state = 0;
         sessionData.lowestLowSinceOpenRange = low[i];
         sessionData.highestHighSinceOpenRange = high[i];
         sessionData.lowestLowTime = time[i];
         sessionData.highestHighTime = time[i];
         
         // Get session name and create appropriate labels
         string sessionName = GetSessionName(newSession);
         string sessionHighLabel = "";
         string sessionLowLabel = "";
         
         if(sessionName == "London") {
            sessionHighLabel = "LN orH";
            sessionLowLabel = "LN orL";
         } else if(sessionName == "New York") {
            sessionHighLabel = "NY orH";
            sessionLowLabel = "NY orL";
         } else if(sessionName == "Sydney") {
            sessionHighLabel = "SY orH";
            sessionLowLabel = "SY orL";
         } else if(sessionName == "Asia") {
            sessionHighLabel = "AS orH";
            sessionLowLabel = "AS orL";
         }
         
         // Create session open range labels with anti-cluttering
         CreateOpenRangeLabels("SessionOpenRange", time[i], sessionData.openRangeHigh, sessionData.openRangeLow, sessionHighLabel, sessionLowLabel, sessionTracker);
         
         currentSession = newSession;
         lastSessionChangeTime = time[i];
      }
      
      // Update extremes and check for wave points for each timeframe
      if(i > 0) {
         // Process wave points for sessions
         if(ShowSessionLabels && sessionData.openRangeTime > 0) {
            UpdateExtremesAndProcessWave(i, time, high, low, sessionData, sessionPoints, "S", sessionTracker);
         }
         
         // Process wave points for days
         if(ShowDayLabels && dayData.openRangeTime > 0) {
            UpdateExtremesAndProcessWave(i, time, high, low, dayData, dayPoints, "D", dayTracker);
         }
         
         // Process wave points for weeks
         if(ShowWeekLabels && weekData.openRangeTime > 0) {
            UpdateExtremesAndProcessWave(i, time, high, low, weekData, weekPoints, "W", weekTracker);
         }
         
         // Process wave points for months
         if(ShowMonthLabels && monthData.openRangeTime > 0) {
            UpdateExtremesAndProcessWave(i, time, high, low, monthData, monthPoints, "M", monthTracker);
         }
         
         // Process wave points for years
         if(ShowYearLabels && yearData.openRangeTime > 0) {
            UpdateExtremesAndProcessWave(i, time, high, low, yearData, yearPoints, "Y", yearTracker);
         }
      }
   }
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Create open range labels with anti-cluttering                    |
//+------------------------------------------------------------------+
void CreateOpenRangeLabels(string prefix, datetime time, double high, double low,
                          string highLabel, string lowLabel, PointTracker &tracker) {
   // Create unique names for high and low labels
   string labelNameHigh = prefix + "High_" + TimeToString(time);
   string labelNameLow = prefix + "Low_" + TimeToString(time);
   
   // Check if close to other labels
   bool isHighCluttered = IsLabelCluttered(time, high, tracker);
   bool isLowCluttered = IsLabelCluttered(time, low, tracker);
   
   // Create labels with anti-cluttering adjustments
   double highYOffset = 0;
   double lowYOffset = 0;
   
   if(isHighCluttered) {
      // Increase vertical counter and apply offset for high label
      tracker.verticalCounter++;
      highYOffset = LabelVerticalSpacing * tracker.verticalCounter;
   }
   
   if(isLowCluttered) {
      // Increase vertical counter and apply offset for low label
      tracker.verticalCounter++;
      lowYOffset = -LabelVerticalSpacing * tracker.verticalCounter;
   }
   
   // Create text objects with offsets
   CreateLabel(labelNameHigh, time, high + highYOffset, highLabel, OpenRangeColor);
   CreateLabel(labelNameLow, time, low + lowYOffset, lowLabel, OpenRangeColor);
   
   // Update tracker
   tracker.lastLabelTime = time;
   tracker.lastLabelPrice = (high + low) / 2; // Use middle price as reference
   tracker.lastLabelDirection = 0; // Reset direction
   tracker.verticalCounter = 0;    // Reset counter for next round
}

//+------------------------------------------------------------------+
//| Check if a label would be cluttered at the given position        |
//+------------------------------------------------------------------+
bool IsLabelCluttered(datetime time, double price, const PointTracker &tracker) {
   if(tracker.lastLabelTime == 0) return false; // First label
   
   // Check time proximity (within 10 bars)
   int barsBetween = (int)MathAbs((time - tracker.lastLabelTime) / PeriodSeconds());
   if(barsBetween > 10) return false;
   
   // Check price proximity (within 5 average true range units)
   double atr = iATR(_Symbol, PERIOD_CURRENT, 14, 1);
   double priceDiff = MathAbs(price - tracker.lastLabelPrice);
   
   return priceDiff < (5 * atr);
}

//+------------------------------------------------------------------+
//| Update extremes and process wave points with anti-cluttering     |
//+------------------------------------------------------------------+
void UpdateExtremesAndProcessWave(const int index,
                                 const datetime &time[],
                                 const double &high[],
                                 const double &low[],
                                 TimeframeData &data,
                                 WavePoint &points[],
                                 const string suffix,
                                 PointTracker &tracker) {
   // Update tracking extremes
   if(low[index] < data.lowestLowSinceOpenRange) {
      data.lowestLowSinceOpenRange = low[index];
      data.lowestLowTime = time[index];
   }
   
   if(high[index] > data.highestHighSinceOpenRange) {
      data.highestHighSinceOpenRange = high[index];
      data.highestHighTime = time[index];
   }
   
   // Initial direction determination
   if(data.state == 0) {
      if(low[index] < data.openRangeLow && high[index] <= data.openRangeHigh) {
         // Price moved lower first
         data.directionPositive = false;
         data.state = 1; // Moving to target state
      }
      else if(high[index] > data.openRangeHigh && low[index] >= data.openRangeLow) {
         // Price moved higher first
         data.directionPositive = true;
         data.state = 1; // Moving to target state
      }
   }
   
   // Process based on current state and direction
   switch(data.state) {
      case 1: // Target state
         if(!data.directionPositive) {
            // Negative direction, looking for a high above open range high
            if(high[index] > data.openRangeHigh) {
               // Create target label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "-" + suffix, false, 0, points, suffix + "_Target", tracker);
               
               data.state = 2; // Move to flip state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // Positive direction, looking for a low below open range low
            if(low[index] < data.openRangeLow) {
               // Create target label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "+" + suffix, true, 0, points, suffix + "_Target", tracker);
               
               data.state = 2; // Move to flip state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
         
      case 2: // Flip state
         if(!data.directionPositive) {
            // We're in negative direction, check if price breaks below previous target
            double lastTarget = GetLastTargetPriceWithSign(points, false);
            if(lastTarget > 0 && low[index] < lastTarget) {
               // Create flip label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "-" + suffix, false, 1, points, suffix + "_Flip", tracker);
               
               data.state = 3; // Move to high/low state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // We're in positive direction, check if price breaks above previous target
            double lastTarget = GetLastTargetPriceWithSign(points, true);
            if(lastTarget > 0 && high[index] > lastTarget) {
               // Create flip label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "+" + suffix, true, 1, points, suffix + "_Flip", tracker);
               
               data.state = 3; // Move to high/low state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
         
      case 3: // High/Low state
         if(!data.directionPositive) {
            // We're in negative direction, check if price goes above flip
            double lastFlip = GetLastFlipPriceWithSign(points, false);
            if(lastFlip > 0 && high[index] > lastFlip) {
               // Create low label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "-" + suffix, false, 2, points, suffix + "_Low", tracker);
               
               // Switch direction for new cycle
               data.directionPositive = true;
               data.state = 1; // Back to target state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // We're in positive direction, check if price goes below flip
            double lastFlip = GetLastFlipPriceWithSign(points, true);
            if(lastFlip > 0 && low[index] < lastFlip) {
               // Create high label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "+" + suffix, true, 2, points, suffix + "_High", tracker);
               
               // Switch direction for new cycle
               data.directionPositive = false;
               data.state = 1; // Back to target state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
   }
}

//+------------------------------------------------------------------+
//| Create wave point label with anti-cluttering                     |
//+------------------------------------------------------------------+
void CreateWavePointLabel(datetime time, double price, string text, bool isPositive,
                          int type, WavePoint &points[], string prefix, PointTracker &tracker) {
   // Create base object name
   string objName = prefix + "_" + TimeToString(time);
   
   // Check for cluttering
   bool isCluttered = IsLabelCluttered(time, price, tracker);
   double yOffset = 0;
   
   // Apply anti-cluttering if needed
   if(isCluttered) {
      // Determine offset direction and apply
      int direction = isPositive ? 1 : -1;
      tracker.verticalCounter++;
      yOffset = direction * LabelVerticalSpacing * tracker.verticalCounter;
   }
   
   // Create wave point structure
   WavePoint point;
   point.time = time;
   point.price = price;
   point.label = text;
   point.isPositive = isPositive;
   point.type = type;
   point.objectName = objName;
   AddWavePoint(points, point);
   
   // Create the actual text label on chart with offset
   color labelColor = isPositive ? PositiveColor : NegativeColor;
   CreateLabel(objName, time, price + yOffset, text, labelColor);
   
   // Update tracker
   tracker.lastLabelTime = time;
   tracker.lastLabelPrice = price;
   tracker.lastLabelDirection = isPositive ? 1 : -1;
   
   // Reset vertical counter if we've gone past a threshold
   if(tracker.verticalCounter > 5) {
      tracker.verticalCounter = 0;
   }
}

//+------------------------------------------------------------------+
//| Add a wave point to the array                                   |
//+------------------------------------------------------------------+
void AddWavePoint(WavePoint &points[], WavePoint &point) {
   int size = ArraySize(points);
   ArrayResize(points, size + 1);
   points[size] = point;
}

//+------------------------------------------------------------------+
//| Get the price of the last target point with given sign           |
//+------------------------------------------------------------------+
double GetLastTargetPriceWithSign(WavePoint &points[], bool positive) {
   for(int i = ArraySize(points) - 1; i >= 0; i--) {
      if(points[i].isPositive == positive && points[i].type == 0) {
         return points[i].price;
      }
   }
   return 0; // Not found
}

//+------------------------------------------------------------------+
//| Get the price of the last flip point with given sign             |
//+------------------------------------------------------------------+
double GetLastFlipPriceWithSign(WavePoint &points[], bool positive) {
   for(int i = ArraySize(points) - 1; i >= 0; i--) {
      if(points[i].isPositive == positive && points[i].type == 1) {
         return points[i].price;
      }
   }
   return 0; // Not found
}

//+------------------------------------------------------------------+
//| Create a text label on the chart with improved positioning       |
//+------------------------------------------------------------------+
void CreateLabel(string name, datetime time, double price, string text, color clr) {
   // Check if object already exists to avoid duplicate labels
   if(ObjectFind(0, name) < 0) {
      // Create text object
      ObjectCreate(0, name, OBJ_TEXT, 0, time, price);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      ObjectSetString(0, name, OBJPROP_FONT, LabelFont);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, LabelFontSize);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      
      // Apply horizontal offset to move text slightly away from the price point
      ObjectSetInteger(0, name, OBJPROP_XOFFSET, LabelHorizontalOffset);
   }
}

//+------------------------------------------------------------------+
//| Determine the current trading session based on time              |
//+------------------------------------------------------------------+
ENUM_SESSION_TYPE DetermineCurrentSession(datetime time) {
   MqlDateTime dt;
   TimeToStruct(time, dt);
   
   int hour = dt.hour;
   
   // Check if in London session
   if(hour >= LondonOpenHour && hour < LondonCloseHour) {
      return SESSION_LONDON;
   }
   
   // Check if in New York session
   if(hour >= NewYorkOpenHour && hour < NewYorkCloseHour) {
      return SESSION_NEWYORK;
   }
   
   // Check if in Sydney session (may span across midnight)
   if(SydneyOpenHour > SydneyCloseHour) {
      if(hour >= SydneyOpenHour || hour < SydneyCloseHour) {
         return SESSION_SYDNEY;
      }
   } else {
      if(hour >= SydneyOpenHour && hour < SydneyCloseHour) {
         return SESSION_SYDNEY;
      }
   }
   
   // Check if in Asia session (may span across midnight)
   if(AsiaOpenHour > AsiaCloseHour) {
      if(hour >= AsiaOpenHour || hour < AsiaCloseHour) {
         return SESSION_ASIA;
      }
   } else {
      if(hour >= AsiaOpenHour && hour < AsiaCloseHour) {
         return SESSION_ASIA;
      }
   }
   
   // Default to London if no match
   return SESSION_LONDON;
}

//+------------------------------------------------------------------+
//| Get session name as string                                       |
//+------------------------------------------------------------------+
string GetSessionName(ENUM_SESSION_TYPE session) {
   switch(session) {
      case SESSION_LONDON:  return "London";
      case SESSION_NEWYORK: return "New York";
      case SESSION_SYDNEY:  return "Sydney";
      case SESSION_ASIA:    return "Asia";
      default:             return "Unknown";
   }
}

//+------------------------------------------------------------------+
//| Clean up all objects created by this indicator                   |
//+------------------------------------------------------------------+
void CleanupObjects() {
   ObjectsDeleteAll(0, "Target_");
   ObjectsDeleteAll(0, "Flip_");
   ObjectsDeleteAll(0, "High_");
   ObjectsDeleteAll(0, "Low_");
   ObjectsDeleteAll(0, "DayOpenRange");
   ObjectsDeleteAll(0, "WeekOpenRange");
   ObjectsDeleteAll(0, "MonthOpenRange");
   ObjectsDeleteAll(0, "YearOpenRange");
   ObjectsDeleteAll(0, "SessionOpenRange");
}

//+------------------------------------------------------------------+
//| Deinitialize the indicator                                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
   // Clean up all objects created by this indicator
   CleanupObjects();
}

//+------------------------------------------------------------------+
//| Enhanced function for checking label cluttering                  |
//+------------------------------------------------------------------+
bool IsLabelCluttered(datetime time, double price, const PointTracker &tracker) {
   if(tracker.lastLabelTime == 0) return false; // First label
   
   double atr = iATR(_Symbol, PERIOD_CURRENT, 14, 1);
   
   // Check time proximity (within X bars)
   int barsBetween = (int)MathAbs((time - tracker.lastLabelTime) / PeriodSeconds());
   if(barsBetween < MinBarsBetweenLabels) {
      // Time is too close, check if price is also close
      double priceDiff = MathAbs(price - tracker.lastLabelPrice);
      
      // Dynamic price threshold based on ATR
      double priceThreshold = atr * MinPriceDistanceFactor;
      
      if(priceDiff < priceThreshold) {
         return true; // Too cluttered
      }
   }
   
   // Check for any existing objects in proximity
   int totalObjects = ObjectsTotal(0);
   for(int i = 0; i < totalObjects; i++) {
      string objName = ObjectName(0, i);
      
      // Skip objects that aren't text labels
      if(ObjectGetInteger(0, objName, OBJPROP_TYPE) != OBJ_TEXT) continue;
      
      // Get object properties
      datetime objTime = (datetime)ObjectGetInteger(0, objName, OBJPROP_TIME);
      double objPrice = ObjectGetDouble(0, objName, OBJPROP_PRICE);
      
      // Check if it's too close
      int objBarDiff = (int)MathAbs((time - objTime) / PeriodSeconds());
      double objPriceDiff = MathAbs(price - objPrice);
      
      if(objBarDiff < MinBarsBetweenLabels && objPriceDiff < (atr * MinPriceDistanceFactor)) {
         return true; // Cluttered with an existing object
      }
   }
   
   return false; // Not cluttered
}

//+------------------------------------------------------------------+
//| Find optimal position for label to avoid cluttering              |
//+------------------------------------------------------------------+
void FindOptimalLabelPosition(datetime &time, double &price, int &direction) {
   double atr = iATR(_Symbol, PERIOD_CURRENT, 14, 1);
   double verticalStep = atr * 0.5; // Half ATR for vertical stepping
   
   // Try up to 5 different vertical positions
   for(int i = 1; i <= 5; i++) {
      // Try above the price
      double testPrice = price + (verticalStep * i);
      if(!IsPositionOccupied(time, testPrice)) {
         price = testPrice;
         direction = 1;
         return;
      }
      
      // Try below the price
      testPrice = price - (verticalStep * i);
      if(!IsPositionOccupied(time, testPrice)) {
         price = testPrice;
         direction = -1;
         return;
      }
   }
   
   // If we couldn't find a good vertical position, try horizontal offset
   for(int i = 1; i <= 3; i++) {
      datetime testTime = time + (PeriodSeconds() * i);
      if(!IsPositionOccupied(testTime, price)) {
         time = testTime;
         direction = 0;
         return;
      }
   }
   
   // If all else fails, keep the original position
   direction = 0;
}

//+------------------------------------------------------------------+
//| Check if a position is already occupied by another label         |
//+------------------------------------------------------------------+
bool IsPositionOccupied(datetime time, double price) {
   int totalObjects = ObjectsTotal(0);
   double atr = iATR(_Symbol, PERIOD_CURRENT, 14, 1);
   double priceThreshold = atr * 0.3; // 30% of ATR
   
   for(int i = 0; i < totalObjects; i++) {
      string objName = ObjectName(0, i);
      
      // Skip objects that aren't text labels
      if(ObjectGetInteger(0, objName, OBJPROP_TYPE) != OBJ_TEXT) continue;
      
      // Get object properties
      datetime objTime = (datetime)ObjectGetInteger(0, objName, OBJPROP_TIME);
      double objPrice = ObjectGetDouble(0, objName, OBJPROP_PRICE);
      
      // Check if it's too close (3 bars and 30% ATR)
      int objBarDiff = (int)MathAbs((time - objTime) / PeriodSeconds());
      double objPriceDiff = MathAbs(price - objPrice);
      
      if(objBarDiff < 3 && objPriceDiff < priceThreshold) {
         return true; // Position is occupied
      }
   }
   
   return false; // Position is free
}

//+------------------------------------------------------------------+
//| Create open range labels with improved anti-cluttering           |
//+------------------------------------------------------------------+
void CreateOpenRangeLabels(string prefix, datetime time, double high, double low,
                          string highLabel, string lowLabel, PointTracker &tracker) {
   // Create unique names for high and low labels
   string labelNameHigh = prefix + "High_" + TimeToString(time);
   string labelNameLow = prefix + "Low_" + TimeToString(time);
   
   // Check if close to other labels and find optimal positions
   datetime highTime = time;
   datetime lowTime = time;
   double highPrice = high;
   double lowPrice = low;
   int highDirection = 0;
   int lowDirection = 0;
   
   if(IsLabelCluttered(highTime, highPrice, tracker)) {
      FindOptimalLabelPosition(highTime, highPrice, highDirection);
   }
   
   if(IsLabelCluttered(lowTime, lowPrice, tracker)) {
      FindOptimalLabelPosition(lowTime, lowPrice, lowDirection);
   }
   
   // Create text objects with found positions
   CreateLabel(labelNameHigh, highTime, highPrice, highLabel, OpenRangeColor);
   CreateLabel(labelNameLow, lowTime, lowPrice, lowLabel, OpenRangeColor);
   
   // Update tracker with the last created label
   tracker.lastLabelTime = time;
   tracker.lastLabelPrice = (high + low) / 2; // Use middle price as reference
   tracker.lastLabelDirection = 0; // Reset direction
   tracker.verticalCounter = 0;    // Reset counter for next round
}

//+------------------------------------------------------------------+
//| Create wave point label with improved anti-cluttering            |
//+------------------------------------------------------------------+
void CreateWavePointLabel(datetime time, double price, string text, bool isPositive,
                          int type, WavePoint &points[], string prefix, PointTracker &tracker) {
   // Create base object name
   string objName = prefix + "_" + TimeToString(time);
   
   // Find optimal position to avoid cluttering
   datetime labelTime = time;
   double labelPrice = price;
   int direction = 0;
   
   if(IsLabelCluttered(labelTime, labelPrice, tracker)) {
      FindOptimalLabelPosition(labelTime, labelPrice, direction);
   }
   
   // Create wave point structure
   WavePoint point;
   point.time = time;
   point.price = price;
   point.label = text;
   point.isPositive = isPositive;
   point.type = type;
   point.objectName = objName;
   AddWavePoint(points, point);
   
   // Create the actual text label on chart
   color labelColor = isPositive ? PositiveColor : NegativeColor;
   CreateLabel(objName, labelTime, labelPrice, text, labelColor);
   
   // Update tracker
   tracker.lastLabelTime = labelTime;
   tracker.lastLabelPrice = labelPrice;
   tracker.lastLabelDirection = direction;
}

//+------------------------------------------------------------------+
//| Create a text label on the chart with improved positioning       |
//+------------------------------------------------------------------+
void CreateLabel(string name, datetime time, double price, string text, color clr) {
   // Check if object already exists to avoid duplicate labels
   if(ObjectFind(0, name) < 0) {
      // Create text object
      ObjectCreate(0, name, OBJ_TEXT, 0, time, price);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      ObjectSetString(0, name, OBJPROP_FONT, LabelFont);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, LabelFontSize);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      
      // Apply horizontal offset to move text slightly away from the price point
      ObjectSetInteger(0, name, OBJPROP_XOFFSET, LabelHorizontalOffset);
      
      // Make sure the label is on top of chart
      ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
   }
}

//+------------------------------------------------------------------+
//| Update extremes and process wave points with improved cluttering |
//+------------------------------------------------------------------+
void UpdateExtremesAndProcessWave(const int index,
                                 const datetime &time[],
                                 const double &high[],
                                 const double &low[],
                                 TimeframeData &data,
                                 WavePoint &points[],
                                 const string suffix,
                                 PointTracker &tracker) {
   // Update tracking extremes
   if(low[index] < data.lowestLowSinceOpenRange) {
      data.lowestLowSinceOpenRange = low[index];
      data.lowestLowTime = time[index];
   }
   
   if(high[index] > data.highestHighSinceOpenRange) {
      data.highestHighSinceOpenRange = high[index];
      data.highestHighTime = time[index];
   }
   
   // Initial direction determination
   if(data.state == 0) {
      if(low[index] < data.openRangeLow && high[index] <= data.openRangeHigh) {
         // Price moved lower first
         data.directionPositive = false;
         data.state = 1; // Moving to target state
      }
      else if(high[index] > data.openRangeHigh && low[index] >= data.openRangeLow) {
         // Price moved higher first
         data.directionPositive = true;
         data.state = 1; // Moving to target state
      }
   }
   
   // Process based on current state and direction
   switch(data.state) {
      case 1: // Target state
         if(!data.directionPositive) {
            // Negative direction, looking for a high above open range high
            if(high[index] > data.openRangeHigh) {
               // Create target label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "-" + suffix, false, 0, points, suffix + "_Target", tracker);
               
               data.state = 2; // Move to flip state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // Positive direction, looking for a low below open range low
            if(low[index] < data.openRangeLow) {
               // Create target label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "+" + suffix, true, 0, points, suffix + "_Target", tracker);
               
               data.state = 2; // Move to flip state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
         
      case 2: // Flip state
         if(!data.directionPositive) {
            // We're in negative direction, check if price breaks below previous target
            double lastTarget = GetLastTargetPriceWithSign(points, false);
            if(lastTarget > 0 && low[index] < lastTarget) {
               // Create flip label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "-" + suffix, false, 1, points, suffix + "_Flip", tracker);
               
               data.state = 3; // Move to high/low state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // We're in positive direction, check if price breaks above previous target
            double lastTarget = GetLastTargetPriceWithSign(points, true);
            if(lastTarget > 0 && high[index] > lastTarget) {
               // Create flip label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "+" + suffix, true, 1, points, suffix + "_Flip", tracker);
               
               data.state = 3; // Move to high/low state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
         
      case 3: // High/Low state
         if(!data.directionPositive) {
            // We're in negative direction, check if price goes above flip
            double lastFlip = GetLastFlipPriceWithSign(points, false);
            if(lastFlip > 0 && high[index] > lastFlip) {
               // Create low label at the lowest low with anti-cluttering
               CreateWavePointLabel(data.lowestLowTime, data.lowestLowSinceOpenRange,
                                  "-" + suffix, false, 2, points, suffix + "_Low", tracker);
               
               // Switch direction for new cycle
               data.directionPositive = true;
               data.state = 1; // Back to target state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         } else {
            // We're in positive direction, check if price goes below flip
            double lastFlip = GetLastFlipPriceWithSign(points, true);
            if(lastFlip > 0 && low[index] < lastFlip) {
               // Create high label at the highest high with anti-cluttering
               CreateWavePointLabel(data.highestHighTime, data.highestHighSinceOpenRange,
                                  "+" + suffix, true, 2, points, suffix + "_High", tracker);
               
               // Switch direction for new cycle
               data.directionPositive = false;
               data.state = 1; // Back to target state
               
               // Reset tracking for next phase
               data.lowestLowSinceOpenRange = low[index];
               data.highestHighSinceOpenRange = high[index];
               data.lowestLowTime = time[index];
               data.highestHighTime = time[index];
            }
         }
         break;
   }
}

//+------------------------------------------------------------------+
//| Determine the current trading session based on time              |
//+------------------------------------------------------------------+
ENUM_SESSION_TYPE DetermineCurrentSession(datetime time) {
   MqlDateTime dt;
   TimeToStruct(time, dt);
   
   int hour = dt.hour;
   
   // Check if in London session
   if(hour >= LondonOpenHour && hour < LondonCloseHour) {
      return SESSION_LONDON;
   }
   
   // Check if in New York session
   if(hour >= NewYorkOpenHour && hour < NewYorkCloseHour) {
      return SESSION_NEWYORK;
   }
   
   // Check if in Sydney session (may span across midnight)
   if(SydneyOpenHour > SydneyCloseHour) {
      if(hour >= SydneyOpenHour || hour < SydneyCloseHour) {
         return SESSION_SYDNEY;
      }
   } else {
      if(hour >= SydneyOpenHour && hour < SydneyCloseHour) {
         return SESSION_SYDNEY;
      }
   }
   
   // Check if in Asia session (may span across midnight)
   if(AsiaOpenHour > AsiaCloseHour) {
      if(hour >= AsiaOpenHour || hour < AsiaCloseHour) {
         return SESSION_ASIA;
      }
   } else {
      if(hour >= AsiaOpenHour && hour < AsiaCloseHour) {
         return SESSION_ASIA;
      }
   }
   
   // Default to London if no match
   return SESSION_LONDON;
}

//+------------------------------------------------------------------+
//| Get session name as string                                       |
//+------------------------------------------------------------------+
string GetSessionName(ENUM_SESSION_TYPE session) {
   switch(session) {
      case SESSION_LONDON:  return "London";
      case SESSION_NEWYORK: return "New York";
      case SESSION_SYDNEY:  return "Sydney";
      case SESSION_ASIA:    return "Asia";
      default:             return "Unknown";
   }
}

//+------------------------------------------------------------------+
//| Add a wave point to the array                                   |
//+------------------------------------------------------------------+
void AddWavePoint(WavePoint &points[], WavePoint &point) {
   int size = ArraySize(points);
   ArrayResize(points, size + 1);
   points[size] = point;
}

//+------------------------------------------------------------------+
//| Get the price of the last target point with given sign           |
//+------------------------------------------------------------------+
double GetLastTargetPriceWithSign(WavePoint &points[], bool positive) {
   for(int i = ArraySize(points) - 1; i >= 0; i--) {
      if(points[i].isPositive == positive && points[i].type == 0) {
         return points[i].price;
      }
   }
   return 0; // Not found
}

//+------------------------------------------------------------------+
//| Get the price of the last flip point with given sign             |
//+------------------------------------------------------------------+
double GetLastFlipPriceWithSign(WavePoint &points[], bool positive) {
   for(int i = ArraySize(points) - 1; i >= 0; i--) {
      if(points[i].isPositive == positive && points[i].type == 1) {
         return points[i].price;
      }
   }
   return 0; // Not found
}

 

Breakaware, copyright2012©

No comments:

Post a Comment

Express Yourself, Be Gentle...

FBS