//+------------------------------------------------------------------+
// Forecast the next bar
// http://www.investorguide.com/article/13283/range-finder-forecasting-the-next-bar-high-and-low-wc/
// http://www.forexfactory.com/showthread.php?p=9265313#post9265313
// To find the next bar low, you add today's high + low + close, then divide the sum by 3, then
// multiply the total by 2, and from that figure you subtract today's high.
// To find the next bar high, use the same initial equation but rather than the high,
// subtract the current bar {low}.
// Next Low (H+L+C/3)x2-H
// Next High (H + L + C/3)x2-L
//+------------------------------------------------------------------+
#property copyright "LukeB"
#property link      "http://www.forexfactory.com/lukeb"
#property strict
//
#include <stdlib.mqh>
//
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 clrMediumSeaGreen
#property indicator_color2 clrRosyBrown
#property indicator_width1 1
#property indicator_width2 1
#property indicator_style1 STYLE_SOLID
#property indicator_style2 STYLE_SOLID
//
const string shortName = "BarRangeForecast";
double indi_barTop[];
double indi_barBottom[];
//
enum ENUM_BAR_PREDICTION_MODE {ALL_DAY_BARS, RELATIVE_TF, SAME_TIME_BAR};
enum ENUM_PREDICTION_MODE { BAR_HIGH, BAR_LOW };
//
extern ENUM_BAR_PREDICTION_MODE PREDICTION_MODE = SAME_TIME_BAR;
//
//------- Constants for common values ------------------
const int ZEROSTART    = 0;
const int NOSHIFT      = 0;
const int BARZERO      = 0;
const int CHARTWINDOW  = 0;
const int BARSTOEXTEND = 30;
const int NOTIME       = NULL;  // Same as zero
const string  NL       = "\n";
//
////void RangeLine(ENUM_PREDICION_MODE HIGH_LOW, color lineColor, int textSize); // A Pulblic Constructor
enum ENUM_DISPLAY_LEVELS         {TOP_M1,   TOP_M5,   TOP_M15,   TOP_M30,   TOP_H1,   TOP_H4,   TOP_D1,   TOP_W1,   TOP_MN1,
                                  LOW_M1,   LOW_M5,   LOW_M15,   LOW_M30,   LOW_H1,   LOW_H4,   LOW_D1,   LOW_W1,   LOW_MN1, endDisplayPeriods};
ENUM_PREDICTION_MODE lineMode[]= {BAR_HIGH, BAR_HIGH, BAR_HIGH,  BAR_HIGH,  BAR_HIGH, BAR_HIGH, BAR_HIGH, BAR_HIGH, BAR_HIGH,
                                  BAR_LOW,  BAR_LOW,  BAR_LOW,   BAR_LOW,   BAR_LOW,  BAR_LOW,  BAR_LOW,  BAR_LOW,  BAR_LOW   };
color lineClr[]                = {clrLime,  clrLime,  clrLime,   clrLime,   clrLime,  clrLime,  clrLime,  clrLime,  clrLime,
                                  clrBisque,clrBisque,clrBisque, clrBisque, clrBisque,clrBisque,clrBisque,clrBisque,clrBisque };
color labelClr[]               = {clrBisque,clrBisque,clrBisque, clrBisque, clrBisque,clrBisque,clrBisque,clrBisque,clrBisque,
                                  clrLime,  clrLime,  clrLime,   clrLime,   clrLime,  clrLime,  clrLime,  clrLime,  clrLime   };
ENUM_TIMEFRAMES forecastTF[]   = {PERIOD_M1,PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1,PERIOD_W1,PERIOD_MN1,
                                  PERIOD_M1,PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H4,PERIOD_D1,PERIOD_W1,PERIOD_MN1};
int   labelSize                =  11;
//
//+-------------------------------------------------------------------------------------------+
//| ------- Line Management Object -------------                                                          |                                                        
//+-------------------------------------------------------------------------------------------+
class RangeLine  // There will be an instance of this class for each range line
 {
   private:
      const static int          uniqueNumber;    // A Number to ensure names are unique to this indicator
      const static ENUM_ANCHOR_POINT  textAnchorValue; // Text associaed with the lines will be anchored here
      static int                lineNumber;      // Number incremented with each instance creation to make unique line names
      datetime                  barZeroTime;     // New Bar Detection for updateing line endpoints and numbers.
      ENUM_TIMEFRAMES           rangePeriod;     // Period this line is for
      ENUM_PREDICTION_MODE      lineMode;        // BAR_HIGH or BAR_LOW line
      //
      string          lineName;             // Name of the line
      color           lineColor;            // Color of ghe line
      int             lineWeight;           // weight of the line
      ENUM_LINE_STYLE lineStyle;            // The style of he line
      datetime        startTime, endTime;   // Time for the line to start and end
      double          linePrice;            // Price for the line to start and end; line is always horizontal, one price needed.
      //
      string          lineLabelText;        // Text to display on the line
      string          labelName;            // Name of the label objet
      color           labelColor;           // Color to make the label
      int             labelSize;            // Font size of the label object
      string          labelStyle;           // Font style of the label object.
      // 
      void GetLabelAnchorPoint(datetime&, double&); // create the time and price values to anchor the lable object
      void SetTheEndTime(void);             // Sets line End Point based on the Chart (not range period) timeframe.
   protected:
      void CreateLine(void);                // Create the line and set it's attributes
      void MoveLine(void);                  // Put the line in positions
      void CreateLineLabelText(void);       // Place the label on the display
      void MakeLineLabelObject(void);       // Place the label on the display
      void MoveLineLabel(void);             // Move the lable to the current position
   public:
      void ~RangeLine(void);                // A Public Destructor
      void RangeLine(ENUM_PREDICTION_MODE HIGH_LOW, ENUM_TIMEFRAMES tf, color lineColor, color lblClr, int textSize); // A Pulblic Constructor
      void BarZeroUpdates(void);      // Set the start and end point values
      string GetInstanceInfo(void);         // Get an instance information string
 };
const static int RangeLine::uniqueNumber                  = 863;   // Initialize static (global) class unique number
const static ENUM_ANCHOR_POINT RangeLine::textAnchorValue  = ANCHOR_RIGHT_LOWER;
static int RangeLine::lineNumber = 0;       // used to make uniqe names for each line.
RangeLine *managedLines[];                  // Make an array of the lines
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
 {
   int initSucceeded = INIT_SUCCEEDED;
   //--- create timer
   EventSetTimer(1);
   //
   IndicatorShortName(shortName);
   //
   SetIndexBuffer( 0, indi_barTop );
   SetIndexStyle( 0, DRAW_LINE );
   SetIndexLabel( 0, "Bar-Top" );
   SetIndexEmptyValue( 0, EMPTY_VALUE );
   //
   SetIndexBuffer( 1, indi_barBottom );
   SetIndexStyle( 1, DRAW_LINE );
   SetIndexLabel( 1, "Bar-Bottom" );
   SetIndexEmptyValue( 1, EMPTY_VALUE );
   //
   IndicatorDigits( Digits+1 );
   //
   if(ArrayResize(managedLines,endDisplayPeriods)< endDisplayPeriods )  // Make the array large enough to hold the new instance of RangeLine.
    {
      initSucceeded = INIT_FAILED;
      Print("Memory Alloscation for Line drawing failed: ",GetLastError(),", ",ErrorDescription(GetLastError()));
    }
   else
    {
      for(ENUM_DISPLAY_LEVELS i = 0; i < endDisplayPeriods; i++)  // Loop through all the possible line objects
       {
            managedLines[i] = new RangeLine(lineMode[i], forecastTF[i], lineClr[i], labelClr[i], labelSize);  // make a range line object.
       }
    }
   //
   return( initSucceeded );
   //
 }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
 {
   EventKillTimer();                    // Clean up on Exit
   //
   for(ENUM_DISPLAY_LEVELS i = 0; i < endDisplayPeriods; i++)  // Loop through all the possible line objects
    {
         delete managedLines[i];
    }
   Comment("");
 }
//+------------------------------------------------------------------+
//| expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()  //timer should be every minute to coincide with the creation of new bars
 {
    for(ENUM_DISPLAY_LEVELS i = 0; i < endDisplayPeriods; i++)  // Loop through all the possible line objects
    {
         managedLines[i].BarZeroUpdates();
    }
   // string debugString = StringConcatenate("High Bar 0: ",indi_barTop[0],", High Bar 1: ",indi_barTop[1],", High Bar 2: ",indi_barTop[2],NL,
   //                                        "Low Bar 0: ",indi_barBottom[0],", Low Bar 1: ",indi_barBottom[1],", Low Bar 2: ",indi_barBottom[2]);
   // Comment(debugString);
 }
//+------------------------------------------------------------------+
//|   On Chart Event                                                 |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,  const long &lparam, const double &dparam,  const string &sparam)
 {
 }
//+------------------------------------------------------------------+
//| 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[] )
 {
   int firstCalcBar = (rates_total-prev_calculated) - 1;
   if ( (firstCalcBar >= 0) )  // If zero or larger, there are new bars
    {
      for(int calcBar = firstCalcBar; calcBar>=0; calcBar--)
       { /*
         static bool commented = false;
         if( (!commented) )
          {
            commented = true;
            Print("Array Size: ",ArraySize(indi_barTop),", Calc Bar: ",calcBar);
          } */
         ENUM_PREDICTION_MODE predictionMode = BAR_HIGH;
         indi_barTop[calcBar] = GetBarPrediction(calcBar, (ENUM_TIMEFRAMES) Period(), predictionMode );
         predictionMode = BAR_LOW;
         indi_barBottom[calcBar] = GetBarPrediction(calcBar, (ENUM_TIMEFRAMES) Period(), predictionMode );
       }
    }
   return( rates_total );
 }
//
//+------------------------------------------------------------------+
double GetBarPrediction(const int& calcBar, const ENUM_TIMEFRAMES tf, const ENUM_PREDICTION_MODE& highOrLow )
 {
   double high, low, close, tfExtreme;
   switch (PREDICTION_MODE)
    {
      case ALL_DAY_BARS:
         setAllDayBars(tf, high,low,close,tfExtreme,calcBar, highOrLow);      // all variables called by reference must be set in the function
         break;
      case RELATIVE_TF:
         setRelativeTFBars(tf, high,low,close,tfExtreme,calcBar, highOrLow);  // all variables called by reference must be set in the function
         break;
      case SAME_TIME_BAR:
         setSameTimeBar(tf, high,low,close,tfExtreme,calcBar, highOrLow);     // all variables called by reference must be set in the function
         break;
      default:
         setRelativeTFBars(tf, high,low,close,tfExtreme,calcBar, highOrLow);  // all variables called by reference must be set in the function
         Print("Error:  SetBarTop called with PREDICTION_MODE set to invalid value: ",IntegerToString(PREDICTION_MODE));
         break;
    }
   // Next Low (H+L+C/3)x2-H
   // Next High (H + L + C/3)x2-L
   return( ((((high + low + close)/3)*2) - tfExtreme) );
   //return( ((high + low + (close/3))*2) - tfExtreme);
 }
void setSameTimeBar(const ENUM_TIMEFRAMES& tf, double& high,double& low,double& close,double& tfExtreme, const int& calcBar, const ENUM_PREDICTION_MODE& extremeMode )  // all called by reference must be set in the function
 {
   ENUM_TIMEFRAMES workingTF = tf;
   high = iHigh(Symbol(),workingTF,calcBar + 1);   // the high of the last completed bar
   low  = iLow(Symbol(),workingTF,calcBar + 1);    // the low of the last completed bar
   close = iClose(Symbol(),workingTF,calcBar + 1); // the close of the last completed bar
   tfExtreme = extremeMode==BAR_HIGH?iLow(Symbol(),workingTF,calcBar+1):iHigh(Symbol(),workingTF,calcBar+1); // Close of the last larger tf bar
 }
void setRelativeTFBars(const ENUM_TIMEFRAMES& tf, double& high,double& low,double& close,double& tfExtreme, const int& calcBar, const ENUM_PREDICTION_MODE& extremeMode )  // all called by reference must be set in the function
 {
   ENUM_TIMEFRAMES workingTF = tf;
   high = iHigh(Symbol(),workingTF,calcBar + 1);   // the high of the last completed bar
   low  = iLow(Symbol(),workingTF,calcBar + 1);    // the low of the last completed bar
   close = iClose(Symbol(),workingTF,calcBar + 1); // the close of the last completed bar
   tfExtreme = extremeMode==BAR_HIGH?iLow(Symbol(),getTFplus1(workingTF),calcBar+1):iHigh(Symbol(),getTFplus1(workingTF),calcBar+1); // Close of the last larger tf bar
 }
ENUM_TIMEFRAMES getTFplus1( const ENUM_TIMEFRAMES& tf)
 {
   ENUM_TIMEFRAMES inputTF = tf;
   ENUM_TIMEFRAMES outputTF;
   if(tf == PERIOD_CURRENT)
    {
      inputTF = (ENUM_TIMEFRAMES) Period();
    }
   switch (inputTF)
    {
      case PERIOD_M1:
         outputTF = PERIOD_M5;
         break;
      case PERIOD_M5:
         outputTF = PERIOD_M15;
         break;
      case PERIOD_M15:
         outputTF = PERIOD_M30;
         break;
      case PERIOD_M30:
         outputTF = PERIOD_H1;
         break;
      case PERIOD_H1:
         outputTF = PERIOD_H4;
         break;
      case PERIOD_H4:
         outputTF = PERIOD_D1;
         break;
      case PERIOD_D1:
         outputTF = PERIOD_W1;
         break;
      case PERIOD_W1:
         outputTF = PERIOD_MN1;
         break;
      case PERIOD_MN1:
         outputTF = PERIOD_MN1;
         break;
      default:
         outputTF = (ENUM_TIMEFRAMES) Period();
         Print( "getTFplus1 called with invalid time frame: ",IntegerToString(tf));
         break;
    }
   return( outputTF );
 }
string getPeriodText( const ENUM_TIMEFRAMES& tf)
 {
   ENUM_TIMEFRAMES inputTF = tf;
   string periodText = "";
   if(tf == PERIOD_CURRENT)
    {
      inputTF = (ENUM_TIMEFRAMES) Period();
    }
   switch (inputTF)
    {
      case PERIOD_M1:
         periodText = "M1";
         break;
      case PERIOD_M5:
         periodText = "M5";
         break;
      case PERIOD_M15:
         periodText = "M15";
         break;
      case PERIOD_M30:
         periodText = "M30";
         break;
      case PERIOD_H1:
         periodText = "H1";
         break;
      case PERIOD_H4:
         periodText = "H4";
         break;
      case PERIOD_D1:
         periodText = "D1";
         break;
      case PERIOD_W1:
         periodText = "W1";
         break;
      case PERIOD_MN1:
         periodText = "MN1";
         break;
      default:
         periodText = "Unknown: "+IntegerToString(tf);
         Print( "getPeriodText called with invalid time frame: ",IntegerToString(tf));
         break;
    }
   return( periodText );
 }
//
void setAllDayBars(const ENUM_TIMEFRAMES& tf, double& high,double& low,double& close,double& tfExtreme, const int& calcBar, const ENUM_PREDICTION_MODE& extremeMode )  // all called by reference must be set in the function
 {
   ENUM_TIMEFRAMES workingTF;
   workingTF = tf > PERIOD_D1?workingTF = tf:workingTF = PERIOD_D1;  // Makes no sense to use D1 on periods larger than D1.
   int dayBarNum = GetNumberOfDayBar( calcBar, workingTF);
   high = iHigh(Symbol(),PERIOD_D1,dayBarNum + 1);   // the high of the last completed bar
   low  = iLow(Symbol(),PERIOD_D1,dayBarNum + 1);    // the low of the last completed bar
   close = iClose(Symbol(),PERIOD_D1,dayBarNum + 1); // the close of the last completed bar
   tfExtreme = extremeMode==BAR_HIGH?iLow(Symbol(),getTFplus1(workingTF),calcBar+1):iHigh(Symbol(),getTFplus1(workingTF),calcBar+1); // Close of the last larger tf bar
 }
int GetNumberOfDayBar(const int& calcBar, const ENUM_TIMEFRAMES& workingTF)
 {
   int dayBarNum = iBarShift(Symbol(),workingTF,Time[calcBar],false);  // I think that for day hours > 12, this will get the next day
   if ( dayBarNum > (iBars(Symbol(),workingTF)-2) )  // cannot start before we get to the 2nd oldest bar
    {
      dayBarNum = iBars(Symbol(),workingTF)-2;
    }
   else if( TimeDay(iTime(Symbol(),workingTF,dayBarNum)) != TimeDay(Time[calcBar]) )  // Go Back one if iBarShift got the next day.
    {
      dayBarNum = dayBarNum + 1;  // the previous day
    }
   return( dayBarNum );
 }
//+-------------------------------------------------------------------------------------------+
//| --------- RangeLine Class Function Definitions -----------                                |
//+-------------------------------------------------------------------------------------------+
void RangeLine::BarZeroUpdates(void)  // Make updates that must be done when there is a new bar zero for the rangePeriod
 {
   if( barZeroTime != iTime(Symbol(),rangePeriod,NOSHIFT) )  // is there a new bar zero?  Only need to update forecast on a new bar zero.
    {
      barZeroTime = iTime(Symbol(),rangePeriod,NOSHIFT);
      linePrice = GetBarPrediction(BARZERO, rangePeriod, lineMode);
    }
    startTime = Time[0]+(Period()*60);       // offset the start a little.
    SetTheEndTime();
    MoveLine();
    MoveLineLabel();
 }
void RangeLine::SetTheEndTime(void)
 {
   endTime   = TimeCurrent() + (Period()*60*BARSTOEXTEND);  // extend the line past tge chart's bar zero (is likely a differet timeframe from rangePeriod).
 }
void RangeLine::CreateLine(void)   // This actually draws the line.
 {
   long chartID = ChartID();
   if ( ObjectFind(chartID,lineName) < 0 )  // ObjectFind returns negative # if no object found
    {
      ObjectCreate(chartID,lineName,OBJ_TREND,CHARTWINDOW,startTime,linePrice,endTime,linePrice); // user will know if there is no line.  But, an error check is common.
      ObjectSetInteger(chartID,lineName,OBJPROP_COLOR,lineColor);
      ObjectSetInteger(chartID,lineName,OBJPROP_STYLE,lineStyle);
      ObjectSetInteger(chartID,lineName,OBJPROP_WIDTH,lineWeight);
      ObjectSetInteger(chartID,lineName,OBJPROP_RAY,false);          //  If you want the line to extend to the right edge, make this true
      ObjectSetInteger(chartID,lineName,OBJPROP_BACK,true);  
      ObjectSetInteger(chartID,lineName,OBJPROP_SELECTED,false); 
      ObjectSetInteger(chartID,lineName,OBJPROP_SELECTABLE,true);
      ObjectSetInteger(chartID,lineName,OBJPROP_HIDDEN,false);
    } else
    {
      MoveLine();   // If it already exists, just move it to its expected position.
    }
 }
void RangeLine::MoveLine(void)   // Function to move the line when there is a new bar - or on timer, restore it to position if user has messed with it.
 {
   bool successZero, successOne;
   if (ObjectFind(lineName)>-1)  // Object find returns 0 or positive # if object is found
    {
      successZero = ObjectMove(lineName,0,startTime,linePrice);  // I've never seen a fail, so doing nothing with the success check.
      successOne = ObjectMove(lineName,1,endTime,linePrice);
    }
   else
    {
      CreateLine();
    }
 }
void RangeLine::CreateLineLabelText(void)  // Make the text for the line label
 {
   string periodText = getPeriodText(rangePeriod);
   string highLowTxt = (lineMode==BAR_HIGH)?"Bar High":"Bar Low";
   lineLabelText = periodText+" "+highLowTxt+" "+DoubleToStr(linePrice,Digits);
 }
void RangeLine::MakeLineLabelObject(void)  // Draw the label on its line
 {
   long chartID = ChartID();
   if ( ObjectFind(chartID,labelName) < 0 )
    {
      datetime anchorTime;
      double anchorPrice;
      GetLabelAnchorPoint(anchorTime, anchorPrice);
      ObjectCreate(chartID,labelName,OBJ_TEXT,CHARTWINDOW,anchorTime,anchorPrice); // user will know if there is no text.  But, an error check is common.
      ObjectSetString(chartID,labelName,OBJPROP_TEXT,lineLabelText); 
      ObjectSetString(chartID,labelName,OBJPROP_FONT,labelStyle); 
      ObjectSetInteger(chartID,labelName,OBJPROP_FONTSIZE,labelSize); 
      ObjectSetDouble(chartID,labelName,OBJPROP_ANGLE,0.0);
      ObjectSetInteger(chartID,labelName,OBJPROP_ANCHOR,textAnchorValue);
      ObjectSetInteger(chartID,labelName,OBJPROP_COLOR,lineColor); 
      ObjectSetInteger(chartID,labelName,OBJPROP_BACK,true); 
      ObjectSetInteger(chartID,labelName,OBJPROP_SELECTABLE,true); 
      ObjectSetInteger(chartID,labelName,OBJPROP_SELECTED,false); 
      ObjectSetInteger(chartID,labelName,OBJPROP_HIDDEN,false);
    }
   else
    {
      MoveLineLabel();  // If it already exists, just display it
    }
 }
void RangeLine::MoveLineLabel(void)            // Move the lable to the current position
 {
   long chartID = ChartID();
   if ( ObjectFind(chartID,labelName) < 0 )  // If it doesn't exist, makeit.
    {
      MakeLineLabelObject();
    }
   else
    {
      datetime anchorTime;
      double anchorPrice;
      GetLabelAnchorPoint(anchorTime, anchorPrice);                     // Set the time and price where the text line will be ancored.
      ObjectSetString(chartID,labelName,OBJPROP_TEXT,lineLabelText);    // update the text
      ObjectMove(chartID,labelName,CHARTWINDOW,anchorTime,anchorPrice); // Place the lable on the chart
    }
 }
void RangeLine::GetLabelAnchorPoint( datetime& anchorTime, double& anchorPrice )  // Set the time and price where the text line will be ancored.
 {
   anchorTime = endTime;
   anchorPrice = linePrice + (Point);
 }
void RangeLine::~RangeLine() // A Public Destructor
 {
   ObjectDelete(lineName);    // Delete the on-chart object
   ObjectDelete(labelName);   // Delete the on-chart object
 }
void RangeLine::RangeLine(ENUM_PREDICTION_MODE HIGH_LOW, ENUM_TIMEFRAMES tf, color lClr, color lblClr, int textSize) // A Pulblic Constructor
 {
   barZeroTime    = NOTIME;         // Initilize time to zero, ensure first time new bar calculations.
   lineNumber++;                    // each instance will display with its own unique number attached to its objects names.
   lineName       = "NBF_Line"+IntegerToString(uniqueNumber)+"_"+IntegerToString(lineNumber);
   lineColor      = lClr;
   lineStyle      = STYLE_DOT;      // ENUM_LINE_STYLE
   rangePeriod    = tf;
   lineMode       = HIGH_LOW;       // ENUM_PREDICION_MODE
   labelColor     = lblClr;
   labelStyle     = "Calibri";
   labelSize      = textSize;
   labelName      = "NBF_Text"+IntegerToString(uniqueNumber)+"_"+IntegerToString(lineNumber);
   BarZeroUpdates();
   CreateLineLabelText();
   CreateLine();
   MakeLineLabelObject();
 }
string RangeLine::GetInstanceInfo(void)  // no real use for this, but might be usefull in debug for prints.
 {
   return(lineName);
 }
//+-------------------------------------------------------------------------------------------+
//| ---------- End RangeLine Class function definitions                                       |
//+-------------------------------------------------------------------------------------------+
//+-------------------------------------------------------------------------------------------+
//| Indicator End                                                                             |
//+-------------------------------------------------------------------------------------------+