//+------------------------------------------------------------------+
//|                                       MurreyLevels-Dirigible.mq4 |
//|                                       Oleg Fedorov (aka certain) |
//|                              voltIrEvvoyRipyafjamNeidy@gmail.com |
//|                                          Version from 15.12.2017 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Makes a colorful Murray levels, that can be dirigible            |
//| by the Ruler line.                                               |
//|                                                                  |
//| If isNeedAutomake=true, it makes first level complect on the     |
//| high/low of the initialBarsForExtremums interval - or on bars    |
//| that you can specify with firstBarNumber and secondBarNumber.    |
//|                                                                  |
//| You can make new complect by clicking on the label in the        |
//| right upper corner                                               |
//|                                                                  |
//| If you will made Ruler line by hand - you will need to drag it   |
//| to show levels. Number of line must be the same as label shows.  |
//| If you want to draw many lines, you need to change timeframe     |
//| after you will draw it all.                                      |
//|                                                                  |
//| If you want to hide some complect of levels on some timeframes,  |
//| you can check this timeframes at the Ruler line                  |
//+------------------------------------------------------------------+

#property copyright "Oleg Fedorov (aka certain)"
#property link      "voltIrEvvoyRipyafjamNeidy@gmail.com"
#property version   "1.21"
#property strict
#property indicator_chart_window

//TODO:
//#include <timeUtilites.mqh>

//--- input parameters
input string   Section_of_parameters_01_="---==== Common parameters ====--- ";
input bool     isNeedAutomake=true; // Automatically create levels complect, if it is nothing yet
input bool     showOnParentTimeframes=false;
//---
input bool     showPrices=true; // Add prices to levels descriptions
input bool     showLevelsLabels=false; // Show additional labels for levels prices
//---
input int      initialBarsForExtremums=50; // How many bars you need for initial complect?
input int      timeCoefficient=5; // How long will be lines?
input string   prefix="MM-dirigible-"; // Which prefix of all indicator's elements?
//---
input string   Section_of_parameters_02_="---==== Initial points numbers ====--- ";
input int      firstBarNumber=0; // First point - If you don't want to start from begin of chart
input int      secondBarNumber=0; // Second point - If you don't want to start from begin of chart
//---
input string   Section_of_parameters_03_="---==== Colors set ====--- ";
input color    rulerColor=clrRed; // Color of ruler line
input color    levelsColor_08=clrRed; // color of 0 and 8 levels
input color    levelsColor_4=clrRed; //  color of 4 level
input color    levelsColor_26=clrLightGreen; // color of 2 and 6 levels
input color    levelsColor_35=clrYellow; // color of 3 and 5 levels
input color    levelsColor_17=clrGreen; // color of all other levels
//---
input string   Section_of_parameters_04_="---==== Widths set ====--- ";
input int      rulerWidth=1; // Width of ruler line
input int      levelsWidth_08=2; // Width of 0 and 8 levels
input int      levelsWidth_4=1; // Width of 4 level
input int      levelsWidth_26=1; // Width of 2 and 6 levels
input int      levelsWidth_35=1; // Width of 3 and 5 levels
input int      levelsWidth_17=1; // Width of all other levels
//---
input string   Section_of_parameters_05_="---==== Styles set ====--- ";
input int      rulerStyle=2; // Style of ruler line
input int      levelsStyle_08=0; // Style of 0 and 8 levels
input int      levelsStyle_4=0; // Style of 4 level
input int      levelsStyle_26=2; // Style of 2 and 6 levels
input int      levelsStyle_35=2; // Style of 3 and 5 levels
input int      levelsStyle_17=2; // Style of all other levels

//--- global variables
int      complectNumber=0;
string   complectSuffix="Complect#";
string   allComplectsName[];

string   rulerLineShortName="Ruler Line";
string   levelShortName="Level";

int      firstPointBarNumber=0;
int      secondPointBarNumber=0;
double   firstPointPrice,secondPointPrice;
datetime firstPointTime,secondPointTime;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   string   objectName="";
   int      i;
   int      maximumBarNumber=0;
   int      minimumBarNumber=0;

//--- indicator buffers mapping
//--- Data initialization
   ChartSetInteger(0,CHART_EVENT_OBJECT_DELETE,true);

//TODO: Set it as a separate function (collect all complects)
   objectName=GetFullObjectName(rulerLineShortName,complectNumber);
   if(ObjectFind(objectName)!=-1)
     {
      for(i=0;ObjectFind(objectName)!=-1;i++)
        {
         complectNumber=i+1;
         ArrayResize(allComplectsName,complectNumber);
         allComplectsName[i]=objectName;
         DelCurrentLineLevels(objectName);
         DrawMMLevels(objectName);
         objectName=GetFullObjectName(rulerLineShortName,complectNumber);
        }
     }
   else 
     {
      if(isNeedAutomake) 
        {
         MakeRuler();
        }
     }
   DelCurrentLineLevels(objectName);
   DrawMMLevels(objectName);
   setNextRulerLabel(); //TODO: Make it universal (without TF-change needs)
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete("MM-next-Ruler");
  }
//+------------------------------------------------------------------+
//| 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[])
  {
//---

//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   string pref="";
   int elementNumber;
   string elementNameSuffix=StringSubstr(
                                         sparam,
                                         StringFind(sparam,"-",StringLen(prefix+complectSuffix))+1,
                                         0);
   string elementNamePrefix=StringSubstr(sparam,0,StringLen(prefix));

//---
   switch(id) 
     {
      case CHARTEVENT_OBJECT_DRAG: 
        {
         if(elementNamePrefix==prefix && elementNameSuffix==rulerLineShortName)
           {
            DelCurrentLineLevels(sparam);
            DrawMMLevels(sparam);
           }
         break;
        }
      case CHARTEVENT_OBJECT_DELETE: 
        {
         if(elementNamePrefix==prefix && elementNameSuffix==rulerLineShortName)
           {
            DelCurrentLineLevels(sparam);
            allComplectsName[elementNumber]="";
           }
         break;
        }
      case CHARTEVENT_OBJECT_CLICK: 
        {
         if(sparam=="MM-next-Ruler")
           {
            MakeRuler();
            DrawMMLevels(
                         GetFullObjectName(rulerLineShortName,
                         complectNumber-1
                         )
                         );
            setNextRulerLabel();
           }
         break;
        }
     }
  }
//+------------------------------------------------------------------+
//| Draw any trendline on the chart                                  |
//+------------------------------------------------------------------+
void DrawLine(string      objectName,
              datetime    _firstPointTime,
              double      _firstPointPrice,
              datetime    _secondPointTime,
              double      _secondPointPrice,
              color       lineColor,
              int         lineStyle=2,
              int         lineWidth=1,
              bool        isRay=false
              )
  {
//---
   ObjectCreate(objectName,OBJ_TREND,0,
                _firstPointTime,_firstPointPrice,
                _secondPointTime,_secondPointPrice);
   ObjectSetInteger(0,objectName,OBJPROP_COLOR,lineColor);
   ObjectSetInteger(0,objectName,OBJPROP_WIDTH,lineWidth);
   ObjectSetInteger(0,objectName,OBJPROP_STYLE,lineStyle);
   ObjectSetInteger(0,objectName,OBJPROP_RAY,isRay);

  }
//+------------------------------------------------------------------+
//| Draw all Murray Levels                                           |
//+------------------------------------------------------------------+
void DrawMMLevels(string rulerFullName)
  {
   string   levelFullName;
   string   currentLevelName;
   string   currentLabelName;

   double   trendVolatility;
   double   levelAmount;
   int      lineLength;
   int      i;

   color    currentColor;
   int      currentWidth;
   int      currentStyle;
   double   currentPrice;
   long     currentTimeframes;

   datetime secondLevelTime;
//int      secondDayOfWeek;
//---
   if(ObjectFind(rulerFullName)!=-1)
     {
      //--- Initiations
      CalculateStartingPoints(rulerFullName);
      trendVolatility=firstPointPrice-secondPointPrice;
      levelAmount= trendVolatility/8;
      lineLength = Period()*60*MathAbs(firstPointBarNumber-secondPointBarNumber)*timeCoefficient;
      secondLevelTime=GetTimeWithShift(firstPointTime+lineLength);
      currentTimeframes=ObjectGetInteger(0,rulerFullName,OBJPROP_TIMEFRAMES);

      //--- Drawing
      for(i=-2;i<11;i++)
        {
         currentLevelName=levelShortName+" "+IntegerToString(i);
         levelFullName=GetFullObjectName(currentLevelName,GetComplectNumber(rulerFullName));
         switch(i) 
           {
            case 0:case 8:
              {
               currentColor = levelsColor_08;
               currentWidth = levelsWidth_08;
               currentStyle = levelsStyle_08;
               break;
              }
            case 3:case 5:
              {
               currentColor = levelsColor_35;
               currentWidth = levelsWidth_35;
               currentStyle = levelsStyle_35;
               break;
              }
            case 2:case 6:
              {
               currentColor = levelsColor_26;
               currentWidth = levelsWidth_26;
               currentStyle = levelsStyle_26;
               break;
              }
            case 4:
              {
               currentColor = levelsColor_4;
               currentWidth = levelsWidth_4;
               currentStyle = levelsStyle_4;
               break;
              }
            default:
              {
               currentColor = levelsColor_17;
               currentWidth = levelsWidth_17;
               currentStyle = levelsStyle_17;
              }
           }
         currentPrice=firstPointPrice-levelAmount*i;
         DrawLine(levelFullName,firstPointTime,currentPrice,
                  secondLevelTime,currentPrice,
                  currentColor,currentStyle,currentWidth);
         ObjectSetInteger(0,levelFullName,OBJPROP_TIMEFRAMES,currentTimeframes);
         if(showPrices)
           {
            ObjectSetText(levelFullName,DoubleToString(currentPrice,Digits())+" "+GetTimeframeName(currentTimeframes));
            if(showLevelsLabels)
              {
               currentLabelName=GetFullObjectName(
                                                  "Label"+IntegerToString(i),
                                                  GetComplectNumber(rulerFullName)
                                                  );
               ObjectCreate(currentLabelName,OBJ_ARROW_LEFT_PRICE,0,firstPointTime,currentPrice);
               ObjectSetInteger(0,currentLabelName,OBJPROP_TIMEFRAMES,currentTimeframes);
               ObjectSetInteger(0,currentLabelName,OBJPROP_COLOR,currentColor);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+
//| Utility function - get full name from the short one.             |
//+------------------------------------------------------------------+
string GetFullObjectName(string shortName,int complect)
  {
//---
   return(prefix +
          complectSuffix+
          IntegerToString(complect)+
          "-"+shortName
          );
  }
//+------------------------------------------------------------------+
//| Utility function - get element number in array - or -1, if it    |
//| does not exists.                                                 |
//+------------------------------------------------------------------+
int ArraySearch(const string &array[],string needle)
  {
   int elementNumber=-1;
   int i;
   int range=ArrayRange(array,0);
//---
   for(i=0;i<range;i++)
     {
      if(array[i]==needle)
        {
         elementNumber=i;
         break;
        }
     }
   return (elementNumber);
  }
//+------------------------------------------------------------------+
//| Utility function - get complect number by element name.          |
//+------------------------------------------------------------------+
int GetComplectNumber(string elementName,
                      string numberDelimiter="#",
                      string commonDeliniter="-")
  {
   string splits[];

//---
   StringSplit(elementName,
               StringGetCharacter(numberDelimiter,0),
               splits);
   return (
           StrToInteger(
           StringSubstr(splits[1],0,
           StringFind(splits[1],
           commonDeliniter)
           )
           )
           );
  }
//+------------------------------------------------------------------+
//| Utility function - delete levels by ruler line full name.        |
//+------------------------------------------------------------------+
void DelCurrentLineLevels(string rulerName)
  {
   string pref,currentGroup;
//---
   pref=StringSubstr(
                     rulerName,0,
                     StringLen(rulerName)-StringLen(rulerLineShortName)
                     );
   currentGroup=pref+levelShortName;
   ObjectsDeleteAll(0,currentGroup);
   currentGroup=pref+"Label";
   ObjectsDeleteAll(0,currentGroup);
  }
//+------------------------------------------------------------------+
//| Utility function - calculates from which pont we start to draw   |
//| levels.                                                          |
//+------------------------------------------------------------------+
void CalculateStartingPoints(string rulerFullName)
  {
   double   price1,price2;
   datetime time1,time2;
   int      bar1,bar2;
//---
   if(ObjectFind(rulerFullName)!=-1)
     {
      price1= ObjectGetDouble(0,rulerFullName,OBJPROP_PRICE1);
      time1 = ObjectGetTimeByValue(0,rulerFullName,price1);
      bar1=ObjectGetShiftByValue(rulerFullName,price1);
      price2= ObjectGetDouble(0,rulerFullName,OBJPROP_PRICE2);
      time2 = ObjectGetTimeByValue(0,rulerFullName,price2);
      bar2=ObjectGetShiftByValue(rulerFullName,price2);
      if(time1<time2)
        {
         firstPointTime=time1;
         firstPointBarNumber=bar1;
         secondPointTime=time2;
         secondPointBarNumber=bar2;
         firstPointPrice=price1;
         secondPointPrice=price2;
        }
      else 
        {
         firstPointTime=time2;
         firstPointBarNumber=bar2;
         secondPointTime=time1;
         secondPointBarNumber=bar1;
         firstPointPrice=price2;
         secondPointPrice=price1;
        }
     }
   else 
     {
      firstPointTime=Time[1];
      firstPointBarNumber=1;
      secondPointTime=Time[1];
      secondPointBarNumber=1;
      firstPointPrice=High[1];
      secondPointPrice=Low[0];
     }
  }
//+------------------------------------------------------------------+
//| Makes ruler line                                                 |
//+------------------------------------------------------------------+
void MakeRuler()
  {
   string   objectName="";
   int      i;
   int      maximumBarNumber=0;
   int      minimumBarNumber=0;
//---
   objectName=GetFullObjectName(rulerLineShortName,complectNumber);
   if(ObjectFind(0,objectName)!=-1 || !isNeedAutomake) return;

//---
   if(firstBarNumber==0 && secondBarNumber==0) 
     {
      maximumBarNumber=ArrayMaximum(High,initialBarsForExtremums);
      minimumBarNumber=ArrayMinimum(Low,initialBarsForExtremums);
      if(maximumBarNumber==0)
        {
         for(i=3;i<initialBarsForExtremums;i++)
           {
            if(High[i]>High[i+1] && 
               High[i]>High[i+2] && 
               High[i]>High[i-1] && 
               High[i]>High[i-2])
              {
               maximumBarNumber=i;
               break;
              }
           }
        }
      if(minimumBarNumber==0)
        {
         for(i=3;i<initialBarsForExtremums;i++)
           {
            if(Low[i]<Low[i+1] && 
               Low[i]<Low[i+2] && 
               Low[i]<Low[i-1] && 
               Low[i]<Low[i-2])
              {
               minimumBarNumber=i;
               break;
              }
           }
        }
      if(minimumBarNumber>maximumBarNumber)
        {
         firstPointBarNumber=minimumBarNumber;
         secondPointBarNumber=maximumBarNumber;
        }
      else 
        {
         firstPointBarNumber=maximumBarNumber;
         secondPointBarNumber=minimumBarNumber;
        }
     }
   else 
     {
      if(firstBarNumber>secondBarNumber)
        {
         firstPointBarNumber=firstBarNumber;
         secondPointBarNumber=secondBarNumber;
        }
      else 
        {
         firstPointBarNumber=secondBarNumber;
         secondPointBarNumber=firstBarNumber;
        }
     }
//---
   if(Low[firstPointBarNumber]<Low[firstPointBarNumber-1] && 
      High[firstPointBarNumber]<High[secondPointBarNumber])
     {
      DrawLine(objectName,
               Time[firstPointBarNumber],Low[firstPointBarNumber],
               Time[secondPointBarNumber],High[secondPointBarNumber],
               rulerColor,rulerStyle,rulerWidth);
     }
   else 
     {
      DrawLine(objectName,
               Time[firstPointBarNumber],High[firstPointBarNumber],
               Time[secondPointBarNumber],Low[secondPointBarNumber],
               rulerColor,rulerStyle,rulerWidth);
     }
   ObjectSetInteger(0,objectName,OBJPROP_SELECTED,true);
//---
   if(!showOnParentTimeframes)
     {
      ObjectSetInteger(0,objectName,OBJPROP_TIMEFRAMES,
                       GetCurrentTimeframes()
                       );
     }
//---
   ArrayResize(allComplectsName,complectNumber+1);
   allComplectsName[complectNumber]=objectName;
   complectNumber++;
   setNextRulerLabel();
//TODO: Make file to save complects with different numbers (not continuose).
  }
//+------------------------------------------------------------------+
//| Crearte label with next ruler number                             |
//+------------------------------------------------------------------+
void setNextRulerLabel()
  {
   if(ObjectFind("MM-next-Ruler")!=-1)
     {
      ObjectDelete("MM-next-Ruler");
     }
   ObjectCreate("MM-next-Ruler",OBJ_LABEL,0,0,
                ChartGetDouble(0,CHART_PRICE_MAX)
                );
   ObjectSetText("MM-next-Ruler","Next MM Ruler number is "+
                 IntegerToString(complectNumber)
                 );
   ObjectSetInteger(0,"MM-next-Ruler",OBJPROP_YDISTANCE,60);
   ObjectSetInteger(0,"MM-next-Ruler",OBJPROP_XDISTANCE,10);
   ObjectSetInteger(0,"MM-next-Ruler",OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,"MM-next-Ruler",OBJPROP_CORNER,CORNER_RIGHT_UPPER);
  }
//+------------------------------------------------------------------+
//| Get timeframes set for current period                            |
//+------------------------------------------------------------------+
long GetCurrentTimeframes()
  {
   long currentTimeframes;

   switch(Period())
     {
      case 1: 
        {
         currentTimeframes=OBJ_PERIOD_M1;
         break;
        }
      case 5: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5;
         break;
        }
      case 15: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15;
         break;
        }
      case 30: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15|
                           OBJ_PERIOD_M30;
         break;
        }
      case 60: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15|
                           OBJ_PERIOD_M30|OBJ_PERIOD_H1;
         break;
        }
      case 240: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15|
                           OBJ_PERIOD_M30|OBJ_PERIOD_H1|OBJ_PERIOD_H4;
         break;
        }
      case 1440: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15|
                           OBJ_PERIOD_M30|OBJ_PERIOD_H1|OBJ_PERIOD_H4|OBJ_PERIOD_D1;
         break;
        }
      case 10080: 
        {
         currentTimeframes=OBJ_PERIOD_M1|OBJ_PERIOD_M5|OBJ_PERIOD_M15|
                           OBJ_PERIOD_M30|OBJ_PERIOD_H1|OBJ_PERIOD_H4|OBJ_PERIOD_D1|
                           OBJ_PERIOD_W1;
         break;
        }
      default: 
        {
         currentTimeframes=OBJ_ALL_PERIODS;
        }
     }
//---
   return (currentTimeframes);
  }
//+------------------------------------------------------------------+
//| Get name of current time frame                                   |
//+------------------------------------------------------------------+
string GetTimeframeName(long minutesInFrame)
  {
   string timeFrameName="";
//---

   switch(minutesInFrame)
     {
      case 1:     timeFrameName = "1m";break;
      case 3:     timeFrameName = "5m";break;
      case 7:     timeFrameName = "15m";break;
      case 15:    timeFrameName = "30m";break;
      case 31:    timeFrameName = "1h";break;
      case 63:    timeFrameName = "4h";break;
      case 127:   timeFrameName = "1D";break;
      case 255:   timeFrameName = "1W";break;
      case 511:   timeFrameName = "1Mn";
     }
//---
   return (timeFrameName);
  }
//+------------------------------------------------------------------+
//| Function return right point of chart if end point of figure is   |
//| on Saturday or Sunday                                            |
//+------------------------------------------------------------------+
datetime GetTimeWithShift(datetime shiftedTime)
  {
   datetime farTime=shiftedTime;
   int shift=60*Period()*(1440/Period());

//---

   if(TimeLocal()-shiftedTime>=shift*2)
     {
      if(TimeDayOfWeek(farTime)==0)
        {
         farTime+=shift;
        }
      if(TimeDayOfWeek(farTime)==6)
        {
         farTime+=shift+shift;
        }
     }

   return (farTime);
  }

//+------------------------------------------------------------------+
