//+------------------------------------------------------------------+
//|             Multi_Pair_xxDayHiLoBreakout_Scanner_Alerts_v1.1.mq4 |
//|                                         Copyright 2019, NickBixy |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, NickBixy"
#property link      "https://www.forexfactory.com/showthread.php?t=904734"
//#property version   "1.00"
#property strict
#property description "Indicator Scans Multiple Symbol Pairs Looking For When The Price Crosses A xDay Breakout Level or x points near Then It Alerts The Trader."
#property indicator_chart_window
#define HR2400 (PERIOD_D1 * 60)

enum yesnoChoiceToggle
  {
   No,
   Yes
  };
enum alertChoiceToggle
  {
   alert,//Alerts Only (Popup alert)
   notification,//Notifications Only (Mobile alert)
   both//Both Alerts and Notifications
  };
enum alertMode
  {
   Both,//Both - Cross And Near Breakout Alerts
   Cross_Alerts_Only,//Crossed only Breakout Alerts
   Near_Alerts_Only,//Near Only Breakout Alerts
  };
input string info="Put Scanner On A Separate Chart Tab From Trading Strategy";//Put Scanner On A Separate Chart Tab From Trading Stategy
input string indiLink="https://www.forexfactory.com/showthread.php?t=904734";//Indicator's thread on Forex Factory
input int refreshTime=5;//Refresh check every x Seconds
input string breakoutHeader="-----------------DayHiLo Breakout Settings------------------------------------------";//----- DayHiLo Breakout Settings
input int numDays=1;//#Days back to find Highest-Lowest
input alertMode alertModeSeclection=Cross_Alerts_Only;//Alert Mode to use
input int xxPoints=50;//Points Near Breakout - For Alert Mode Near

input string symbolHeader="-----------------Symbol Settings------------------------------------------";//----- Symbol Settings
input yesnoChoiceToggle useSymbolsMarketWatch=No;//Use Symbols in Market Watch(Laggy if many symbols)
input string symbols="AUDCAD,AUDCHF,AUDJPY,AUDNZD,AUDUSD,CADCHF,CADJPY,CHFJPY,EURAUD,EURCAD,EURCHF,EURGBP,EURJPY,EURNZD,EURUSD,GBPAUD,GBPCAD,GBPCHF,GBPJPY,GBPNZD,GBPUSD,NZDCAD,NZDCHF,NZDJPY,NZDUSD,USDCAD,USDCHF,USDJPY"; //Symbols To Scan
input string symbolPrefix=""; //Symbol Prefix
input string symbolSuffix=""; //Symbol Suffix

input string AlertHeader="-----------------Alert Settings----------------------------------------------";//----- Alert Settings
input int alertInternalMinutes=60;//Alert Wait Time In Min for same exact alert Msg
input  alertChoiceToggle  alertOptions=both;//Alert Notification Options
input yesnoChoiceToggle showBidPriceOnAlert=No;//Show Bid Price in Alert Msg
input yesnoChoiceToggle printOutValue=Yes;//Print Out HiLow Values - For Testing Values
input int printOutValueSymbolIndex=0;//Index Value Of Symbol To Print Out

int numSymbols=0; //the number of symbols to scan
int alertIntervalTimeSeconds; //wait time between same alert message for pivot point

string symbolList[]; // array of symbols
string symbolListFinal[]; // array of symbols after merging post and prefix
datetime symbolTodaysDate[]; //array of symbol dates today used for checking for new day for each symbol

double dailyHiLoLevels[][2]; //stores all the symbols dayHiLi Levels
double dailyHiLoLevelsCheck[][2]; //stores all the symbols dayHiLi Levels

bool dailyHiLoLevelsFlag[][2];
bool dailyHiLoLevelsZoneFlag[][2];
datetime   dailyHiLoLevelsCrossedWaitTill[][2];
datetime   dailyHiLoLevelsNearWaitTill[][2];

string pointsNearMessage="is Near";//PointsNear Alert Msg
string crossedMessage="Crossed";//Cross Alert Msg

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {

   IndicatorSetString(INDICATOR_SHORTNAME,"MultiPair DHLBreakout Scanner Alerts");//name of indicator used for when symbol not found error will remove indicator from chart

   if(numDays<1)
     {
      Alert("#Days back can't be less than 1\nREMOVING INDICATOR FROM CHART.");
      ChartIndicatorDelete(0,0,"MultiPair DHLBreakout Scanner Alerts");
     }

   if(EventSetTimer(refreshTime)==false)
     {
      if(_LastError!=0)
        {
         PrintFormat("Failed: %i",_LastError);
         ResetLastError();
        }
     }

   alertIntervalTimeSeconds=alertInternalMinutes*60;//waiting time between alerts

   if(useSymbolsMarketWatch==Yes)
     {
      int numSymbolsMarketWatch=SymbolsTotal(true);
      numSymbols=numSymbolsMarketWatch;
      ArrayResize(symbolListFinal,numSymbolsMarketWatch);
      for(int i=0; i<numSymbolsMarketWatch; i++)
        {
         symbolListFinal[i]=SymbolName(i,true);
        }
     }
   else
      if(useSymbolsMarketWatch==No)
        {
         getSymbols();//converts the symbol string to list of symbols
        }

   if(testSymbols())//checks if all symbols exits, if not removes indicator from chart and alert message
     {
      Initialize();
     }

   return(INIT_SUCCEEDED);
  }
void  OnDeinit(const int  reason)
  {
   for(int i=0; i<numSymbols; i++)
     {
      symbolTodaysDate[i]=NULL;
      resetVariables(i);
     }
   Print(__FUNCTION__," Deinitialization reason code = ",reason);
  }
int      TimeOfDay(datetime when=0)
  {
   if(when == 0)
      when = TimeCurrent();
   return(int(when % HR2400));
  }
datetime DateOfDay(datetime when=0)
  {
   if(when == 0)
      when = TimeCurrent();
   return(when - TimeOfDay(when));
  }
bool     download_history(string symbol, ENUM_TIMEFRAMES period=PERIOD_CURRENT)
  {
   if(period == PERIOD_CURRENT)
      period = (ENUM_TIMEFRAMES)_Period;
   ResetLastError();
   datetime other = iTime(symbol, period, 0);
   if(_LastError == 0 && other != 0)
      return true;
   if(_LastError != ERR_HISTORY_WILL_UPDATED
      && _LastError != ERR_NO_HISTORY_DATA
     )
      PrintFormat("iTime(%s,%i) Failed: %i", symbol, period, _LastError);
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer()//this one loops every x seconds checking each symbol for the alert conditions or when new day to refresh new pivot point values
  {
   for(int symbolIndex=0; symbolIndex<numSymbols; symbolIndex++)
     {
      if(!download_history(symbolListFinal[symbolIndex],PERIOD_D1))
        {
         continue;
        }
      else
        {
         if(IsNewDay(symbolIndex))//check if new day or if is null its also true
           {
            InitializeDayHiLoLevels(symbolIndex);
           }
         checkValuesForValid(symbolIndex);
         checkFlags(symbolIndex);//alert if flags change
        }
     }//end of for loop
  }
int start()
  {
   return 0;
  }
void Initialize()
  {
//resize to the number of symbols being used
   ArrayResize(dailyHiLoLevels,numSymbols);
   ArrayResize(dailyHiLoLevelsCheck,numSymbols);
   ArrayResize(dailyHiLoLevelsFlag,numSymbols);
   ArrayResize(dailyHiLoLevelsZoneFlag,numSymbols);
   ArrayResize(dailyHiLoLevelsCrossedWaitTill,numSymbols);
   ArrayResize(dailyHiLoLevelsNearWaitTill,numSymbols);
   ArrayResize(symbolTodaysDate,numSymbols);

   for(int symbolIndex=0; symbolIndex<numSymbols; symbolIndex++)//loop all symols get value and clear waitTilles,set flags for cross and near
     {
      symbolTodaysDate[symbolIndex]=NULL;
     }//end of for loop
  }
void InitializeDayHiLoLevels(int symbolIndex)
  {

   if(symbolTodaysDate[symbolIndex]==NULL)
     {
      resetVariables(symbolIndex);//reset all values for symbol

      symbolTodaysDate[symbolIndex]=iTime(symbolListFinal[symbolIndex],PERIOD_D1,0);//used to find out new day

      double highest=iHigh(symbolListFinal[symbolIndex],PERIOD_D1,1);
      dailyHiLoLevels[symbolIndex][0]=highest;

      double lowest=iLow(symbolListFinal[symbolIndex],PERIOD_D1,1);
      dailyHiLoLevels[symbolIndex][1]=lowest;
      int digits=(int)MarketInfo(symbolListFinal[symbolIndex],MODE_DIGITS);

      if(numDays>1)
        {
         for(int i=1; i<=numDays; i++)
           {
            if(highest<iHigh(symbolListFinal[symbolIndex],PERIOD_D1,i))
              {
               highest=iHigh(symbolListFinal[symbolIndex],PERIOD_D1,i);
               dailyHiLoLevels[symbolIndex][0]=highest;
              }
            if(lowest>iLow(symbolListFinal[symbolIndex],PERIOD_D1,i))
              {
               lowest=iLow(symbolListFinal[symbolIndex],PERIOD_D1,i);
               dailyHiLoLevels[symbolIndex][1]=lowest;
              }
           }
        }


      if(lowest==NULL ||
         highest==NULL ||
         iClose(symbolListFinal[symbolIndex], PERIOD_D1,0)==0 ||
         iTime(symbolListFinal[symbolIndex], PERIOD_D1, 0)==0)
        {
         symbolTodaysDate[symbolIndex]=NULL;
         Print(symbolListFinal[symbolIndex]+" " +EnumToString(PERIOD_D1) +" History Still Downloading");
         return;
        }

      if(printOutValue==Yes && printOutValueSymbolIndex==symbolIndex)
        {
         printOutValuesTest(printOutValueSymbolIndex);
        }

      setFlags(symbolIndex);
     }


  }
void getSymbols()//method extracts the symbols from the string turns it to a list then adds on the correct prefix and suffix so can find symbols
  {
   string sep=",";
   ushort u_sep;
   u_sep=StringGetCharacter(sep,0);
   StringSplit(symbols,u_sep,symbolList);

   numSymbols=ArraySize(symbolList);//get the number of how many symbols are in the symbolList array

   ArrayResize(symbolListFinal,numSymbols);//resize finals symbol list to correct size

   for(int symbolIndex=0; symbolIndex<numSymbols; symbolIndex++) //combines postfix , symbol , prefix names together
     {
      symbolListFinal[symbolIndex]=symbolPrefix+symbolList[symbolIndex]+symbolSuffix;
     }
  }
bool testSymbols()//try to find all the symbols from list if not error message
  {
   bool result=true;
   for(int symbolIndex=0; symbolIndex<numSymbols; symbolIndex++)
     {
      double bid=MarketInfo(symbolListFinal[symbolIndex],MODE_BID);

      if(_LastError==4106) // unknown symbol
        {
         result=false;
         Alert("Can't find this symbol: "+symbolListFinal[symbolIndex]+",\nPut symbols in Market Watch,\nDouble Check Prefix Or Suffix Settings,\nREMOVING INDICATOR FROM CHART.");
         PrintFormat("Failed: %i",_LastError);
         ChartIndicatorDelete(0,0,"MultiPair DHLBreakout Scanner Alerts");
         break;
        }
     }
   return result;
  }
void resetVariables(int symbolIndex)//resets all pivot wait tills when new day
  {
   dailyHiLoLevels[symbolIndex][0]=NULL;
   dailyHiLoLevels[symbolIndex][1]=NULL;

   dailyHiLoLevelsFlag[symbolIndex][0]=NULL;
   dailyHiLoLevelsFlag[symbolIndex][1]=NULL;

   dailyHiLoLevelsZoneFlag[symbolIndex][0]=NULL;
   dailyHiLoLevelsZoneFlag[symbolIndex][1]=NULL;

   dailyHiLoLevelsCrossedWaitTill[symbolIndex][0]=NULL;
   dailyHiLoLevelsCrossedWaitTill[symbolIndex][1]=NULL;

   dailyHiLoLevelsNearWaitTill[symbolIndex][0]=NULL;
   dailyHiLoLevelsNearWaitTill[symbolIndex][1]=NULL;
  }
bool IsNewDay(int symbolIndex)//checks if new day
  {
   bool result=false;
   if(symbolTodaysDate[symbolIndex]!=iTime(symbolListFinal[symbolIndex],PERIOD_D1,0) || symbolTodaysDate[symbolIndex]==NULL)
     {
      symbolTodaysDate[symbolIndex]=NULL;
      result=true;
     }
   else
     {
      result=false;
     }
   return result;
  }
//+------------------------------------------------------------------+
void setFlags(int symbolIndex)
  {
   double bid=MarketInfo(symbolListFinal[symbolIndex],MODE_BID);
   double points=MarketInfo(symbolListFinal[symbolIndex],MODE_POINT);
   double pipPoints=xxPoints*points;

   if(alertModeSeclection==Cross_Alerts_Only ||
      alertModeSeclection==Both)
     {
      for(int i=0; i<2; i++) //sets the initial flags
        {
         if(bid>=dailyHiLoLevels[symbolIndex][i])
            dailyHiLoLevelsFlag[symbolIndex][i]=true;
         else
            dailyHiLoLevelsFlag[symbolIndex][i]=false;
        }
     }

   if(alertModeSeclection==Near_Alerts_Only ||
      alertModeSeclection==Both)
     {
      for(int i=0; i<2; i++) //sets the initial flags
        {
         double zoneHigh=dailyHiLoLevels[symbolIndex][i]+pipPoints;
         double zoneLow=dailyHiLoLevels[symbolIndex][i]-pipPoints;

         if(bid<=zoneHigh && bid>=zoneLow)//check if inzone == false
           {
            dailyHiLoLevelsZoneFlag[symbolIndex][i]=true;
           }
         else
           {
            dailyHiLoLevelsZoneFlag[symbolIndex][i]=false;
           }
        }
     }
  }
void checkFlags(int symbolIndex)
  {
   if(symbolTodaysDate[symbolIndex]!=NULL)
     {
      bool result;//bool for bid>pivot test with flag
      double bid;
      double points;
      double pipPoints;
      int digits;

      string symbolName=symbolListFinal[symbolIndex];

      bid=MarketInfo(symbolName,MODE_BID);
      points=MarketInfo(symbolName,MODE_POINT);
      digits=(int)MarketInfo(symbolName,MODE_DIGITS);
      pipPoints=xxPoints*points;

      for(int j=0; j<2; j++)
        {
         if(alertModeSeclection==Cross_Alerts_Only ||
            alertModeSeclection==Both)
           {
            result=bid>=dailyHiLoLevels[symbolIndex][j];
            if(result!=dailyHiLoLevelsFlag[symbolIndex][j])
              {
               dailyHiLoLevelsFlag[symbolIndex][j]=result;
               if(TimeCurrent()>=dailyHiLoLevelsCrossedWaitTill[symbolIndex][j])
                 {
                  dailyHiLoLevelsCrossedWaitTill[symbolIndex][j]=(TimeCurrent()+alertIntervalTimeSeconds);

                  if(j==0)
                    {
                     doAlert(symbolIndex,0,(string)numDays+" Day High");
                    }
                  else
                    {
                     doAlert(symbolIndex,0,(string)numDays+" Day Low");
                    }
                 }
              }
           }
         if(alertModeSeclection==Near_Alerts_Only ||
            alertModeSeclection==Both)
           {
            double zoneHigh=dailyHiLoLevels[symbolIndex][j]+pipPoints;
            double zoneLow=dailyHiLoLevels[symbolIndex][j]-pipPoints;

            result=((bid<=zoneHigh) && (bid>=zoneLow));//get 1 or 0
            if(result!=dailyHiLoLevelsZoneFlag[symbolIndex][j])
              {
               dailyHiLoLevelsZoneFlag[symbolIndex][j]=result;
               if(TimeCurrent()>=dailyHiLoLevelsNearWaitTill[symbolIndex][j] && dailyHiLoLevelsZoneFlag[symbolIndex][j]==true)
                 {
                  dailyHiLoLevelsNearWaitTill[symbolIndex][j]=(TimeCurrent()+alertIntervalTimeSeconds);

                  if(j==0)
                    {
                     doAlert(symbolIndex,1,(string)numDays+ " Day High");
                    }
                  else
                    {
                     doAlert(symbolIndex,1,(string)numDays+" Day Low");
                    }

                 }
              }
           }
        }
     }


  }
void printOutValuesTest(int symbolIndex)
  {
   Alert("PRINT OUT TEST "+symbolListFinal[symbolIndex]+" - "+"HIGH:"+DoubleToString(dailyHiLoLevels[symbolIndex][0],(int)MarketInfo(symbolListFinal[symbolIndex],MODE_DIGITS))+" - "+"LOW:"+DoubleToString(dailyHiLoLevels[symbolIndex][1],(int)MarketInfo(symbolListFinal[symbolIndex],MODE_DIGITS))+"\n"+(string)numDays+" Day");
  }
void doAlert(int symbolIndex,bool crossOrNear,string level)
  {
   if(alertOptions==both)
     {
      if(crossOrNear==0)
        {
         if(showBidPriceOnAlert==No)
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage);
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage);
           }
         else
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
      if(crossOrNear==1)
        {
         if(showBidPriceOnAlert==No)
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage);
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage);
           }
         else
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
     }
   if(alertOptions==notification)
     {
      if(crossOrNear==0)
        {
         if(showBidPriceOnAlert==No)
           {
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage);
           }
         else
           {
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
      if(crossOrNear==1)
        {
         if(showBidPriceOnAlert==No)
           {
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage);
           }
         else
           {
            SendNotification(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
     }
   if(alertOptions==alert)
     {
      if(crossOrNear==0)
        {
         if(showBidPriceOnAlert==No)
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage);
           }
         else
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+crossedMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
      if(crossOrNear==1)
        {
         if(showBidPriceOnAlert==No)
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage);
           }
         else
           {
            Alert(symbolListFinal[symbolIndex]+" "+level+" "+pointsNearMessage+" @ "+(string)SymbolInfoDouble(symbolListFinal[symbolIndex],SYMBOL_BID));
           }
        }
     }
  }
//+------------------------------------------------------------------+
void checkValuesForValid(int symbolIndex)
  {
   double highest=iHigh(symbolListFinal[symbolIndex],PERIOD_D1,1);
   dailyHiLoLevelsCheck[symbolIndex][0]=highest;

   double lowest=iLow(symbolListFinal[symbolIndex],PERIOD_D1,1);
   dailyHiLoLevelsCheck[symbolIndex][1]=lowest;

   if(numDays>1)
     {
      for(int i=1; i<=numDays; i++)
        {
         if(highest<iHigh(symbolListFinal[symbolIndex],PERIOD_D1,i))
           {
            highest=iHigh(symbolListFinal[symbolIndex],PERIOD_D1,i);
            dailyHiLoLevels[symbolIndex][0]=highest;
           }
         if(lowest>iLow(symbolListFinal[symbolIndex],PERIOD_D1,i))
           {
            lowest=iLow(symbolListFinal[symbolIndex],PERIOD_D1,i);
            dailyHiLoLevels[symbolIndex][1]=lowest;
           }
        }
     }
   if(dailyHiLoLevelsCheck[symbolIndex][0]!=dailyHiLoLevels[symbolIndex][0] ||
      dailyHiLoLevelsCheck[symbolIndex][1]!=dailyHiLoLevels[symbolIndex][1])
     {
      symbolTodaysDate[symbolIndex]=NULL;
      Print(symbolListFinal[symbolIndex]+" "+"checkValuesForValid found a outdated value reseting symbol and updating");
     }

  }
//+------------------------------------------------------------------+
