//+------------------------------------------------------------------+
//|                                                    Plot_News.mq4 |
//|                                                   David Louisson |
//|                                        http://www.metaquotes.net |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//|   Reads news from NEWS.CSV file and plots appropriate            |
//|   objects on chart                                               |
//+------------------------------------------------------------------+

// Version history:
// 2008.08.14 v2 (pips4life)  Moved code from init() into start().  The
//    arrows will now update with every new *bar*. (Only the first tick per 
//    bar is processed; the rest are skipped).  This means future arrows
//    will adjust close to the current pip range so they can be seen, and
//    after the news event, the arrow adjusts closer to the bar 
//    automatically.  The arrow placement is now relative to a 2-bar low 
//    (or high) instead of just 1 bar, which helps reduce clutter when price
//    action is steep. In addition, when there are no future FF news
//    events, the user is reminded to run FFcal.exe.  Old arrows are now
//    deleted by both the init() and deinit() sections.
// 2007.05.01 v1 (David Louisson)  First release...

// ENHANCEMENT/BUG LIST:
// * There are no known bugs, nor planned enhancements at present.

/* DISCLAIMER:
This program is provided without any warranty, expressed or implied. The 
program may contain errors that result in wrong calculations and/or faulty 
operation.  The program is not guaranteed to work with any past, present or 
future version of MetaTrader, and no ongoing support is expressed or implied.
The author(s) accept no liability whatsoever for any losses or damages to your 
account, your computer, nor anything else, resulting from the use of this
program or information.  The comments in this program and associated documents 
are for educational purposes only and should not be substituted for the advice 
of a professional broker. Use extreme caution.  Past performance and price action 
does not guarantee similar performance and price action in the future.  Trading 
Foreign Exchange (Forex) carries a high level of risk and may not be suitable 
for all investors. There is a possibility that you could sustain a loss of all 
(100%!) or more of your investment.  Therefore, you should not invest money that
you cannot afford to lose. You should be aware of all the risks associated with
Forex trading and you accept all liabilities.
*/

#property copyright "David Louisson"
#property link      "http://www.metaquotes.net"

#property indicator_chart_window

// NEWS.CSV file must exist in: \metatrader 4\experts\files
extern   string   FileName          = "news.csv";   // News file name
extern   int      VertSpacing       = 5;            // # pips spacing between symbols plotted against same bar
extern   int      SymbolSize        = 2;            // size of symbols being plotted on the chart

// Filters (set parameter to FALSE to suppress plot):
extern   datetime FromDate          = D'1990.01.01 00:00';
extern   datetime ThruDate          = D'2009.12.31 23:59';
extern   bool     Plot_High         = TRUE;         // High impact   = 3 (red plot)
extern   bool     Plot_Medium       = TRUE;         // Medium impact = 2 (orange plot)
extern   bool     Plot_Low          = TRUE;         // Low impact    = 1 (yellow plot)
extern   bool     Plot_USD          = TRUE;         // USD = 1 (wingding character #140)
extern   bool     Plot_CAD          = TRUE;         // CAD = 2 (wingding character #141)
extern   bool     Plot_EUR          = TRUE;         // EUR = 3 (wingding character #142)
extern   bool     Plot_GBP          = TRUE;         // GBP = 4 (wingding character #143)
extern   bool     Plot_CHF          = TRUE;         // CHF = 5 (wingding character #144)
extern   bool     Plot_JPY          = TRUE;         // JPY = 6 (wingding character #145)
extern   bool     Plot_AUD          = TRUE;         // AUD = 7 (wingding character #146)
extern   bool     Plot_NZD          = TRUE;         // NZD = 8 (wingding character #147)
extern   bool     RemindToRunFFcal  = TRUE;         // On Sunday-Thursday, if no future FF arrows, display a REMINDER text

int      handle;
int last_bars = 0;

int      eCount;              // Event counter
datetime eTime[9999];         // Event date/time, format = yyyy.mm.dd<space>hh:mm
int      eNumber[9999];       // Event seq# (same date/time use ascending seq#s)
int      eWingding[9999];     // Wingding code for implicated currency, as above
int      eImpact[9999];       // 1=low impact; 2=medium impact; 3=high impact
string   eCurrency[9999];     // Currency/ies on whose charts object will display; ALL = all currencies 
string   ePeriods[9999];      // Time period settings on which object will display (M1,M5,M15,M30,H1,H4,D1,W1,MTH; 0=no, 1=yes)
string   eText1[9999];        // First descr line, format = hh:mm XXX Y "event description"
                              //   where XXX=currency; Y=H/M/L impact
string   eText2[9999];        // Second descr line, shows Actual, Forecast, Previous values

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
{

  //period = Period();
//----
   
   //Sometimes with crashes, old arrows still exist. Delete them.
   DeleteOldArrows();
   return(0);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
{
//----
   // Delete created objects when indicator is removed
   for(int i=0;i<eCount;i++)
     ObjectDelete(eText1[i]);
   
   //Sometimes with crashes, old arrows still exist. Delete them.
   DeleteOldArrows();
//----
   return(0);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
{
//----
//---- indicators

   // The following will reduce the CPU load by exiting if the last iteration was < 1 bar ago.
   if (Bars - last_bars < 1) return(0);
   datetime timecurrent = TimeCurrent();
   last_bars = Bars;

   bool FLAG_FutureFFEventFound = false;
   string userEventString;
   
   string objname, sym1, sym2, cmp1, cmp2;
   int    barno, dir1;
   double vertpos; 

   handle = FileOpen(FileName, FILE_CSV|FILE_READ,';');          // Open NEWS.CSV
   if(handle==0)
      Comment("File "+FileName+" not found.");

   for(eCount = 0; !FileIsEnding(handle); eCount++)              // Read from NEWS.CSV; one line per event
   {
      eTime[eCount]     = StrToTime(FileReadString(handle));     // Event date/time
      eNumber[eCount]   = StrToInteger(FileReadString(handle));  // Event seq# (same date/time have ascending seq#s)
      eWingding[eCount] = StrToInteger(FileReadString(handle));  // Wingding code denoting implicated currency
      eImpact[eCount]   = StrToInteger(FileReadString(handle));  // Impact: 1=low; 2=medium; 3=High
      eCurrency[eCount] = FileReadString(handle);                // Currency/ies on whose charts object will display; ALL = all currencies
      ePeriods[eCount]  = FileReadString(handle);                // Time period settings on which object will display (M1,M5,M15,M30,H1,H4,D1,W1,MTH; 0=no, 1=yes)
      eText1[eCount]    = FileReadString(handle);                // First line of description
      eText2[eCount]    = FileReadString(handle);                // Second line of description

      if (eNumber[eCount] == 0 )      continue;                  // Invalid entry, or EOF
//    if (eWingding[eCount] == 0 )    continue;
//    if (eImpact[eCount] == 0 )      continue;

      if (eTime[eCount] < FromDate) continue;                    // Skip if outside entered date range
      if (eTime[eCount] > ThruDate) continue;
      
      // Check if this event is a FF (ForexFactory) future event
      // The intent is to set the flag ONLY if a future *FF* event was found. Ignore user-defined events.
      // Another possible check to add to the logic below could be:
      //     Find the last word in eText1[eCount] (the Event Id#). If 1st-char is "*" skip it because it's user-defined.
      //     But if no leading "*", then use StrToInteger and check if > 3000. All FF events since 2007 are above 3000.

      userEventString = "\034 \042";  // User-defined events should contain:  doublequote(ascii=34), space, asterisk(ascii=42)
      //ALT: string userEventString = StringConcatenate( StringSetChar(" ", 0, 34), " ", StringSetChar(" ", 0, 42));
      if (eTime[eCount] > timecurrent // If the event is in the future...
           && eNumber[eCount] > 0  // FF events are always positive.  User-defined events *should* be negative but may not be
           && StringFind( eText1[eCount], userEventString, 0) < 0  // If the event 1st-line does NOT contain the userEventString
         ) FLAG_FutureFFEventFound = TRUE;  // TRUE means a future FF event was found.
      
      if (eImpact[eCount] == 1 && Plot_Low == FALSE)    continue;                     // Skip if impact outside entered values
      if (eImpact[eCount] == 2 && Plot_Medium == FALSE) continue;
      if (eImpact[eCount] == 3 && Plot_High == FALSE)   continue;
      
      if (eWingding[eCount] == 140 && Plot_USD == FALSE)   continue;                  // Skip if currency outside entered values
      if (eWingding[eCount] == 141 && Plot_CAD == FALSE)   continue;
      if (eWingding[eCount] == 142 && Plot_EUR == FALSE)   continue;
      if (eWingding[eCount] == 143 && Plot_GBP == FALSE)   continue;
      if (eWingding[eCount] == 144 && Plot_CHF == FALSE)   continue;
      if (eWingding[eCount] == 145 && Plot_JPY == FALSE)   continue;
      if (eWingding[eCount] == 146 && Plot_AUD == FALSE)   continue;
      if (eWingding[eCount] == 147 && Plot_NZD == FALSE)   continue;

      if ( StringSubstr(ePeriods[eCount],0,1) == "0" && Period() == PERIOD_M1  )   continue;    // Skip if not to be shown on this period's chart
      if ( StringSubstr(ePeriods[eCount],1,1) == "0" && Period() == PERIOD_M5  )   continue;
      if ( StringSubstr(ePeriods[eCount],2,1) == "0" && Period() == PERIOD_M15 )   continue;
      if ( StringSubstr(ePeriods[eCount],3,1) == "0" && Period() == PERIOD_M30 )   continue;
      if ( StringSubstr(ePeriods[eCount],4,1) == "0" && Period() == PERIOD_H1  )   continue;
      if ( StringSubstr(ePeriods[eCount],5,1) == "0" && Period() == PERIOD_H4  )   continue;
      if ( StringSubstr(ePeriods[eCount],6,1) == "0" && Period() == PERIOD_D1  )   continue;
      if ( StringSubstr(ePeriods[eCount],7,1) == "0" && Period() == PERIOD_W1  )   continue;
      if ( StringSubstr(ePeriods[eCount],8,1) == "0" && Period() == PERIOD_MN1 )   continue;

      bool skipflag = FALSE;                                                          // Skip if currency outside entered values
      if (eCurrency[eCount] != "ALL")
        {
          skipflag = TRUE;
          sym1 = StringSubstr(Symbol(),0,3);
          sym2 = StringSubstr(Symbol(),3,3);
          cmp1 = StringSubstr(eCurrency[eCount],0,3);
          if (StringLen(eCurrency[eCount]) > 3) 
          {
            cmp2 = StringSubstr(eCurrency[eCount],3,3);
            if ((cmp1 == "ALL" || cmp1 == sym1 || cmp1 == sym2) && (cmp2 == "ALL" || cmp2 == sym1 || cmp2 == sym2)) skipflag = FALSE;   
          }
          else
          {
            if (cmp1 == "ALL" || cmp1 == sym1 || cmp1 == sym2) skipflag = FALSE;   
          }  
        }
      if (skipflag == TRUE) continue;  

      objname = eText1[eCount];                                                       // Set object name = first descr line
      barno = iBarShift(NULL,0,eTime[eCount],FALSE);                                  // Number of bars back from current
//    if (eWingding[eCount] == 0) barno++;

      dir1 = 1;
      if (eNumber[eCount] < 0) dir1 = -1;
      
      if (VertSpacing * eNumber[eCount] > 0) 
        vertpos = MathMin(iLow(NULL,0,barno), iLow(NULL,0,barno+1))  - VertSpacing * eNumber[eCount] * Point;         // Plot object VertSpacing pips x seq# below low of relevant 2-bars
        //vertpos = iLow(NULL,0,barno) - VertSpacing * eNumber[eCount] * Point;         // Plot object VertSpacing pips x seq# below low of relevant bar
      else
        vertpos = MathMax(iHigh(NULL,0,barno),iHigh(NULL,0,barno+1)) - VertSpacing * (eNumber[eCount]+dir1) * Point;    // Plot object VertSpacing pips x seq# above high of relevant 2-bars
        //vertpos = iHigh(NULL,0,barno) - VertSpacing * (eNumber[eCount]+dir1) * Point;    // Plot object VertSpacing pips x seq# above high of relevant bar
      
      if (eWingding[eCount] == 0)
        {  
         if (ObjectFind(objname) < 0)
           { // Object does not exist yet
            ObjectCreate(objname,OBJ_TEXT,0,eTime[eCount],vertpos);
           }
         else
           { // Object already exists. Adjust T/P
            ObjectSet(objname, OBJPROP_TIME1, eTime[eCount]);
            ObjectSet(objname, OBJPROP_PRICE1, vertpos);
           }
                         
         ObjectSet(objname,OBJPROP_ANGLE,0);
         ObjectSetText(objname,eText2[eCount],9,"Verdana",White);                      // Set object descr = second descr line
        }
      else
        {
        if (ObjectFind(objname) < 0)
           { // Object does not exist yet
            ObjectCreate(objname,OBJ_ARROW,0,eTime[eCount],vertpos);
           }
         else
           { // Object already exists. Adjust T/P
            ObjectSet(objname, OBJPROP_TIME1, eTime[eCount]);
            ObjectSet(objname, OBJPROP_PRICE1, vertpos);
           }
                       
        ObjectSet(objname,OBJPROP_ARROWCODE,eWingding[eCount]);                       // Set wingding code
        ObjectSet(objname,OBJPROP_WIDTH,SymbolSize);                                  // Set size of object
        ObjectSetText(objname,eText2[eCount]);                                        // Set object descr = second descr line
        } 
      
      ObjectSet(objname,OBJPROP_COLOR,White);                                         // Default color
      if (eImpact[eCount] == 1) ObjectSet(objname,OBJPROP_COLOR,Yellow);              // Low impact = yellow
      if (eImpact[eCount] == 2) ObjectSet(objname,OBJPROP_COLOR,Orange);              // Medium impact = orange
      if (eImpact[eCount] == 3) ObjectSet(objname,OBJPROP_COLOR,Red);                 // High impact = red
      if (eImpact[eCount] == 4) ObjectSet(objname,OBJPROP_COLOR,LimeGreen);           // Allow other primary colors
      if (eImpact[eCount] == 5) ObjectSet(objname,OBJPROP_COLOR,DodgerBlue);        
      if (eImpact[eCount] == 6) ObjectSet(objname,OBJPROP_COLOR,Magenta);        
      if (eImpact[eCount] == 7) ObjectSet(objname,OBJPROP_COLOR,Aqua);        
      if (eImpact[eCount] == 8) ObjectSet(objname,OBJPROP_COLOR,Goldenrod);        
      if (eImpact[eCount] == 9) ObjectSet(objname,OBJPROP_COLOR,Plum);        
      if (eImpact[eCount] == 10) ObjectSet(objname,OBJPROP_COLOR,Silver);        

   }
 
   int debug, i;
   debug=FileOpen("debug.txt", FILE_CSV|FILE_WRITE, '|');
   for( i=0; i<eCount; i++)
     FileWrite(debug,i,TimeToStr(eTime[i]),eNumber[i],eWingding[i],eImpact[i],eCurrency[i],eText1[i],eText2[i]);
   FileClose(debug);

   FileClose(handle);
   
   if (ObjectFind("Plot_News_FFcal_Reminder") >= 0) ObjectDelete("Plot_News_FFcal_Reminder");
   //if  (TimeDayOfWeek(timecurrent) <= 4)
   if (RemindToRunFFcal && ! FLAG_FutureFFEventFound && TimeDayOfWeek(timecurrent) <= 4)

     {
      // If this is not Fri or Sat, then create a Text label reminder to run FFcal.exe
      // The assumption is there will always be at least 1 Friday FF event (usually true), 
      //   so that on Thursday or before, at least one future FF event will be found.
      //   But once Friday starts, you don't insist on finding future events.
      //   and don't issue any reminder until the start-of-the-week (Sunday) or later, up to Thur.
      double pr1 = Low[1] - 10*Point; // # pips below previous (not the current) bar Low
      ObjectCreate("Plot_News_FFcal_Reminder", OBJ_TEXT, 0, timecurrent, pr1, 0,0,0,0);
      ObjectSetText("Plot_News_FFcal_Reminder", "REMINDER: Run FFcal.exe to get future events", 12, "Arial", Red);
     }
  
//----
   return(0);
}

void DeleteOldArrows()
  {
   //Sometimes with crashes, old arrows still exist. Delete them.
   string objname;
   int obj_total = ObjectsTotal();
   for(int j = obj_total-1; j >= 0; j--) // for each object, count backwards and do...
     {
      objname = ObjectName(j);
      if ( objname != "" 
          && ObjectType(objname) == OBJ_ARROW 
          && StringFind(objname,":",0) == 2 
          && StringFind(objname," ",0) == 5 
          && StringFind(ObjectDescription(objname),"[",0) == 0
         ) ObjectDelete(objname);
     }
   if (ObjectFind("Plot_News_FFcal_Reminder") >= 0) ObjectDelete("Plot_News_FFcal_Reminder");
   
  } //end of DeleteOldArrows