//+------------------------------------------------------------------+
//|                                 Cliff Jaded - Distance to MA.mq4 | 
//|                             Copyright 2025 Great Teacher Ehsaibo | 
//|                                     www.youtube.com/c/cliffjaded | 
//+------------------------------------------------------------------+
#property copyright     "Copyright 2025 Great Teacher Ehsaibo"
#property link          "www.youtube.com/c/cliffjaded"
#property version       "1.3"
#property description   "Measures distance of price to MA in a separate window."
#property description   "Creates horizontal lines with alerts"
#property description   "Please refer to an ASCII list to change shortcuts"
#property strict

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2

#property indicator_label1  "Positive Distance"
#property indicator_color1  clrGreen
#property indicator_style1  DRAW_HISTOGRAM
#property indicator_width1  2

#property indicator_label2  "Negative Distance"
#property indicator_color2  clrRed
#property indicator_style2  DRAW_HISTOGRAM
#property indicator_width2  2
//+------------------------------------------------------------------+
//| ENUMERATIONS                                                     |
//+------------------------------------------------------------------+
enum MA_Type
{
   MA_SMA = 0,  // Simple Moving Average
   MA_EMA = 1,  // Exponential Moving Average
   MA_SMMA = 2, // Smoothed Moving Average
   MA_LWMA = 3  // Linear Weighted Moving Average
};
enum PriceType
{
   PRICE_CLOSE_CUSTOM  = 0,  // Close
   PRICE_OPEN_CUSTOM   = 1,  // Open
   PRICE_HIGH_CUSTOM   = 2,  // High
   PRICE_LOW_CUSTOM    = 3   // Low
};
//+------------------------------------------------------------------+
//|MACROS                                                            |
//+------------------------------------------------------------------+
#define	ALERT_LABEL       "Alerter Label"
#define  DISTANCE_LABEL    "Current Distance Label"
#define  DISTANCE_VALUE    "Current Distance Value"
#define	ALERT             "_alert"
#define  CUSTOM_LINE       "D4MA"
//+------------------------------------------------------------------+
//| INPUTS                                                           |
//+------------------------------------------------------------------+
// Moving Average Settings
extern string     Header1        = "------Moving Average Settings------";
extern int        MAPeriod       = 100;               // Moving Average Period
extern int        MAShift        = 0;                 // Moving Average Shift
extern MA_Type    MAType         = MA_SMA;            // Moving Average Type
extern PriceType  MAPrice        = PRICE_CLOSE_CUSTOM;// Moving Average Price
// Histogram Settings
extern string     Header2        = "------Histogram Settings------";
extern color      colourPositive = clrGreen;          // Positive color (high above MA)
extern color      colourNegative = clrRed;            // Negative color (low below MA)
extern int        HistogramWidth = 2;                 // Width of the histogram bars
// Alert Settings
extern string     Header3        = "------Alerts------";
extern bool		   PushAlert      = false;
extern bool	   	PopupAlert	   = false;
extern bool		   SoundAlert	   = true;
extern bool       BoldAlert      = true;
extern string     Sound          = "alert.wav";       // Custom sound file (must be in the "Sounds" folder)
// Label Settings
extern string     Header4        = "------Label Settings------";
extern color      LabelColour    = clrRed;
extern int        xOffset        = 200;
extern int        yOffset        = 0;
// Shortcut ASCII Settings
extern int        checkDescriptionsKey = 79;          // o - Toggle Object Descriptions
extern int        createHLineKey = 49;                // 1 - Create Horizontal Line
extern int        addAlertKey    = 73;                // i - Toggle Alert of Selected Object
//+------------------------------------------------------------------+
//| INDICATOR BUFFERS                                                |
//+------------------------------------------------------------------+
double PositiveBuffer[];
double NegativeBuffer[];
double MABuffer[];
//+------------------------------------------------------------------+
//|GLOBAL VARIABLES                                                  |
//+------------------------------------------------------------------+
int      SubWindow;        // Chart ID of current sub window
double   lastMousePrice;
datetime lastMouseTime;
int      objectCounter;    // Counter for unique object names
double   previousDistance; // Store the previous distance for cross detection
//+------------------------------------------------------------------+
//| INITIALISATION                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //---- indicator short name
   IndicatorShortName("Distance(" + IntegerToString(MAPeriod) + "," + GetMATypeString(MAType) + "," + IntegerToString(MAShift) + "," + GetMAPriceString(MAPrice) + ")");

   //---- Set the drawing style for histogram & MA
   SetIndexStyle(0, DRAW_HISTOGRAM);
   SetIndexStyle(1, DRAW_HISTOGRAM);

   //---- Set buffers
   SetIndexBuffer(0, PositiveBuffer);
   SetIndexBuffer(1, NegativeBuffer);

   //---- Set labels for the Data Window
   SetIndexLabel(0, "Distance from High to MA");
   SetIndexLabel(1, "Distance from Low to MA");

   //---- Set the width of the histogram bars and MA
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, HistogramWidth);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, HistogramWidth);
   
   // Enable chart event handling
   ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
   
   //---- Determine Chart Window ID
   SubWindow = ChartWindowFind();
   
   // Initialise previous Distance
   previousDistance = LiveDistance();
   
   // Create indicator labels
   createAlertLabel(ALERT_LABEL);
   createDistanceLabel(DISTANCE_LABEL);
   createDistanceValueLabel(DISTANCE_VALUE);
   
   //---- done
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| DE-INITIALISATION                                                |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(ALERT_LABEL);
   ObjectDelete(DISTANCE_LABEL);
   ObjectDelete(DISTANCE_VALUE);
}
//+------------------------------------------------------------------+
//| INTERATION                                                       |
//+------------------------------------------------------------------+
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[])
{
   // Ensure freshest data by refreshing market rates
   RefreshRates();  // This makes sure we're working with the latest market data

   if (LiveDistance() != previousDistance)
   {
      // Call the function to check lines
      CheckLines();
      // Update current distance value label
      updateDistanceValueLabel(DISTANCE_VALUE);
   }
   
   // Update previousDistance after processing
   previousDistance = LiveDistance();
   
   //---- local variables
   int limit;
   double maValue = 0.0;      // Initialize maValue
   double appliedPrice = 0.0; // Initialize appliedPrice
   
   //---- Check for sufficient data
   if(rates_total < MAPeriod) return(0);
   
   //---- last counted bar will be recalculated
   limit = rates_total - prev_calculated;
   if (prev_calculated > 0) limit++; // Add 1 for the first calculation of new candles
   
   //---- Loop through the bars to calculate the distance
   for (int i = 0; i < limit; i++)
   {
      maValue = iMA(NULL, 0, MAPeriod, MAShift, GetMAType(MAType), GetMAPrice(MAPrice), i);
   
      if (high[i] > maValue)
         PositiveBuffer[i] = NormalizeDouble((high[i] - maValue) / (Point * 10), 1);
      else
         PositiveBuffer[i] = 0;
   
      if (low[i] < maValue)
         NegativeBuffer[i] = -NormalizeDouble((maValue - low[i]) / (Point * 10), 1);
      else
         NegativeBuffer[i] = 0;
   }
   
   //---- Return value for next call
   return(rates_total);
}
//+------------------------------------------------------------------+
//| CHART EVENTS                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // Update last known mouse position on mouse move
   if(id == CHARTEVENT_MOUSE_MOVE)
   {
      int x = (int)lparam;  // Mouse X coordinate (horizontal position)
      int y = (int)dparam;  // Mouse Y coordinate (vertical position)
      
      int sub_window = 0;  // Main chart window
      datetime time;
      double price;
      
      if(ChartXYToTimePrice(0, x, y, sub_window, time, price))
      {
         lastMouseTime = time;
         lastMousePrice = price;
      }
   }
   // Check which key is pressed and create the corresponding horizontal line
   if(id == CHARTEVENT_KEYDOWN)
   {
      //Comment(int(lparam)); // Check ASCII keycode
         
         // Generate a unique object name
         string objName = GenerateUniqueObjectName(CUSTOM_LINE);
         
         // Create solid "mss" line (key '1')
         if((int)lparam == createHLineKey) CreateHLine(objName, lastMousePrice, clrWhite, 1, "", STYLE_SOLID);
         
         // Toggle object descriptions (key 'o')
         if((int)lparam == checkDescriptionsKey)
         {
            // Checks whether object descriptions are on or off       
            if (ChartGetInteger(0, CHART_SHOW_OBJECT_DESCR, 1)) ChartSetInteger(0, CHART_SHOW_OBJECT_DESCR, 0);
            else ChartSetInteger(0, CHART_SHOW_OBJECT_DESCR, 1);
            
            // Force immediate chart refresh
            ChartRedraw();
         }
         
         // Toggle alert for object (key 'i')
         if ((int)lparam == addAlertKey) ToggleAlert();
   }
}
//+------------------------------------------------------------------+
//| CUSTOM FUNCTIONS                                                 |
//+------------------------------------------------------------------+
// Converts MAType Enumerator to Integer
int GetMAType(MA_Type maType)
{
   switch(maType)
   {
      case MA_SMA:   return(MODE_SMA);
      case MA_EMA:   return(MODE_EMA);
      case MA_SMMA:  return(MODE_SMMA);
      case MA_LWMA:  return(MODE_LWMA);
      default:       return(MODE_SMA);
   }
}

// Converts PriceType Enumerator to Integer
int GetMAPrice(PriceType maPrice)
{
   switch(MAPrice)
   {
      case PRICE_CLOSE_CUSTOM:return(PRICE_CLOSE);
      case PRICE_OPEN_CUSTOM: return(PRICE_OPEN);
      case PRICE_HIGH_CUSTOM: return(PRICE_HIGH);
      case PRICE_LOW_CUSTOM:  return(PRICE_LOW);
      default:                return(PRICE_CLOSE);
   }
}

// Converts MAType Enumerator to String
string GetMATypeString(MA_Type maType)
{
   switch(maType)
   {
      case MA_SMA:   return("SMA");
      case MA_EMA:   return("EMA");
      case MA_SMMA:  return("SMMA");
      case MA_LWMA:  return("LWMA");
      default:       return("SMA");
   }
}

// Converts MAPrice Enumerator to String
string GetMAPriceString(PriceType maPrice)
{
   switch(MAPrice)
   {
     case PRICE_CLOSE_CUSTOM: return("Close");
     case PRICE_OPEN_CUSTOM:  return("Open");
     case PRICE_HIGH_CUSTOM:  return("High");
     case PRICE_LOW_CUSTOM:   return("Low");
     default:                 return("Close");
   }
}

// Get Live Distance from MA of current candle based on close price
double LiveDistance()
{
   double ma = iMA(NULL, 0, MAPeriod, MAShift, GetMAType(MAType), GetMAPrice(MAPrice), 0);
   double closePrice = Close[0];
   return NormalizeDouble((closePrice - ma) / (Point * 10), 1);  // Distance in pips
}

// Function to scan lines and trigger alerts
void CheckLines()
{
   for (int i = ObjectsTotal() - 1; i >= 0; i--)
   {
     string objName        = ObjectName(i);
     string objDescription = ObjectGetString(0, objName, OBJPROP_TEXT);
   
     // Check if the object description contains ALERT
     if (StringFind(objDescription, ALERT) >= 0)
     {
         long objType = ObjectGetInteger(0, objName, OBJPROP_TYPE);  // Get object type
   
         if (objType == OBJ_HLINE)  // Horizontal line
         {
            double currentDistance = LiveDistance();
            double linePrice = ObjectGetDouble(0, objName, OBJPROP_PRICE);
   
             if ((previousDistance > linePrice && currentDistance <= linePrice) ||  // Crossed from above to below
                 (previousDistance < linePrice && currentDistance >= linePrice))    // Crossed from below to above
             {
                 TriggerAlert(objName, linePrice);
             }
         }
     }
   }
}

// Function to trigger alert and modify object properties
void TriggerAlert(string objName, double linePrice, int fibLevel = -1, long objType = -1)
{
   string message = Symbol() + ": " + "Price is " + DoubleToString(linePrice,1) + " pips away from the " + IntegerToString(MAPeriod) + " " + GetMATypeString(MAType);
   // Trigger a popup alert and sound notification
   if (PopupAlert)  Alert(message);
   if (SoundAlert)  PlaySound(Sound);
   if (PushAlert)   SendNotification(message);
   
   // Get the current object description
   string objDescription = ObjectGetString(SubWindow, objName, OBJPROP_TEXT);
   string newDescription;
   
   // Clear alert
   if (ObjectDescription(objName) == ALERT) newDescription = "";
   else newDescription = StringSubstr(objDescription, 0, StringFind(objDescription, ALERT));

   ObjectSetString(0, objName, OBJPROP_TEXT, newDescription);

   
   // Make the object bold and solid with a width of 2
   ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
   ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_SOLID);
  
   // Print a message to indicate the alert was triggered
   Print("Alert triggered for: ", objName, ". Alert cleared", ObjectGetString(0, objName, OBJPROP_TEXT));
}

// Create Indicator Label
void createAlertLabel(string sObjName)
{
	// Set the text
	string sObjText = "Alerts: ";
	if (PushAlert)    sObjText = sObjText + "  Push ON.";
	else              sObjText = sObjText + "  Push OFF.";
	if (PopupAlert)   sObjText = sObjText + "  Popup ON.";
	else			      sObjText = sObjText + "  Popup OFF.";
	if (SoundAlert)   sObjText = sObjText + "  Sound ON.";
	else			      sObjText = sObjText + "  Sound OFF.";
	if (BoldAlert)    sObjText = sObjText + "  Bold ON.";
	else              sObjText = sObjText + "  Bold OFF.";
	
	// Create and position the label
	ObjectCreate (sObjName, OBJ_LABEL, SubWindow, 0, 0);
	ObjectSetText(sObjName, sObjText, 8, "Arial", LabelColour);
   ObjectSet    (sObjName, OBJPROP_CORNER, 0);
	ObjectSet    (sObjName, OBJPROP_XDISTANCE, xOffset);
	ObjectSet    (sObjName, OBJPROP_YDISTANCE, yOffset);
	ObjectSet    (sObjName, OBJPROP_SELECTABLE, false);
}

// Create Current Distance Label
void createDistanceLabel(string sObjName)
{
	// Set the text
	string sObjText = "Current Distance:                pips";
	
	// Create and position the label
	ObjectCreate (sObjName, OBJ_LABEL, SubWindow, 0, 0);
	ObjectSetText(sObjName, sObjText, 8, "Arial", clrYellow);
   ObjectSet    (sObjName, OBJPROP_CORNER, 3);
	ObjectSet    (sObjName, OBJPROP_XDISTANCE, 0);
	ObjectSet    (sObjName, OBJPROP_YDISTANCE, 0);
	ObjectSet    (sObjName, OBJPROP_SELECTABLE, false);
}

// Create Current Distance Value Label
void createDistanceValueLabel(string sObjName)
{
	// Set the text
	string sObjText = DoubleToString(LiveDistance(), 1);
	
	// Create and position the label
	ObjectCreate (sObjName, OBJ_LABEL, SubWindow, 0, 0);
	ObjectSetText(sObjName, sObjText, 8, "Arial", clrYellow);
   ObjectSet    (sObjName, OBJPROP_CORNER, 3);
	ObjectSet    (sObjName, OBJPROP_XDISTANCE, 30);
	ObjectSet    (sObjName, OBJPROP_YDISTANCE, 0);
	ObjectSet    (sObjName, OBJPROP_SELECTABLE, false);
}

// Update Current Distance Value Label
void updateDistanceValueLabel(string sObjName)
{
	// Set the text
	string sObjText = DoubleToString(LiveDistance(), 1);
	
	// Update label
	ObjectSetText(sObjName, sObjText, 8, "Arial", clrYellow);
}

// Function to create a unique object name
string GenerateUniqueObjectName(string baseName)
{
   string newName;
   
   while(true)
   {
      // Create the new name by appending objectCounter to the baseName
      newName = baseName + "_" + IntegerToString(objectCounter);
      bool nameExists = false;
      
      for (int i = ObjectsTotal() - 1; i >= 0; i--)
      {
         // Check if the new name matches any existing object name
         if(newName == ObjectName(i))
         {
            nameExists = true;
            break; // No need to check further, we found a match
         }
      }
      
      // If no match is found, return the new unique name
      if(!nameExists)
         return newName;

      // Increment the counter and try again
      objectCounter++;
   }
}

// Function to create horizontal lines with specific properties
void CreateHLine(string name, double price, color lineColor, int width, string description, ENUM_LINE_STYLE style)
{
   // Unselect all other objects
   for (int i = ObjectsTotal() - 1; i >= 0; i--)
   {
      if (ObjectGetInteger(0, ObjectName(i), OBJPROP_SELECTED)) ObjectSetInteger(0, ObjectName(i), OBJPROP_SELECTED, false);
   }

   if (ObjectCreate(0, name, OBJ_HLINE, SubWindow, 0, price))
   {
      ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
      ObjectSetInteger(0, name, OBJPROP_SELECTED, true);
      ObjectSetString(0, name, OBJPROP_TEXT, description);
      Print("Horizontal line created at price: ", price, " with description: ", description, " on current and lower timeframes.");
   }
   else Print("Failed to create horizontal line at price: ", price);
}

// Function to add or remove alert from selected object
void ToggleAlert()
{
   // Loop through all objects on the chart
   for (int i = ObjectsTotal() - 1; i >= 0; i--)
   {
      string objName = ObjectName(i);
      string objDesc = ObjectDescription(objName);
      string newDesc;
      
      if (ObjectGetInteger(0, objName, OBJPROP_SELECTED))
      {
         // Set line width back to 1
         if (ObjectGetInteger(0, objName, OBJPROP_WIDTH, 2)) ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
         
         if (StringFind(objDesc, ALERT) < 0)
         ObjectSetString(0, objName, OBJPROP_TEXT, objDesc + ALERT); // Append ALERT
         
         else
         {
            if (ObjectDescription(objName) == ALERT) newDesc = "";
            else newDesc = StringSubstr(objDesc, 0, StringFind(objDesc, ALERT));
      
            ObjectSetString(0, objName, OBJPROP_TEXT, newDesc);
         }
      }
   }
}