/* ##############################################################################################



** NOT FULLY TESTED. USE WITH CAUTION. COULD BE OPTIMISED FURTHER. **



Header file which helps to solve the problem identified at https://forum.mql4.com/73940#1041728:

when adding a "multi-timeframe" indicator to a visual-mode backtest of an EA in MT4,

the timeseries functions such as iBarShift, iClose, iLowest etc do not work correctly.

(Doing a backtest of such an indicator itself does work, i.e. selecting "Indicator" in 

the MT4 strategy tester. The thing which doesn't work is doing a backtest of an EA 

and then adding the multi-timeframe indicator to the EA's chart. May start working

in future; the MT4 build at the time of writing is 1012.)



This header file fixes the use of the timeseries functions such as iBarShift.

It does **NOT** currently fix the same problem with technical indicator functions

such as iMA, iCCI etc. The functions which the header file does currently fix

are as follows.



   iBars

   iBarShift

   iTime

   iOpen

   iHigh

   iLow

   iClose

   iVolume

   iLowest

   iHighest

   CopyRates         

   CopyOpen          

   CopyHigh          

   CopyLow           

   CopyClose         

   CopyTime          

   CopyTickVolume    

   

The header file does not affect the use of the indicator on a normal, live,

non-backtesting chart. Its behaviour only takes effect in backtesting.

You can therefore have a single version of your indicator for use both

in backtesting and on normal charts, without needing to vary and 

recompile the code.



This header file is only for use with indicators. It is not needed with EAs.



There are two ways of using this header file:



Method A:   Simply #include the header file. This will, in effect, "inject" itself

            into your indicator code, modifying the uses of iBarShift etc so that

            they work in the strategy tester.

            

Method B:   Manual control. This requires three different steps. You **must**

            use this method if your indicator has an old-style start()

            event instead of the OnCalculate() introduced in MT4 build 600.

            1. #define the value MTF_NO_REPLACEMENTS and then include

               the header file. For example:

                  #define MTF_NO_REPLACEMENTS

                  #include <timeseries-visualmode-fixup.mqh>

            2. At the start of your OnCalculate, add the following line of code

                  MTF_UpdateWithLatestTick(TimeCurrent(), close[0]);

            3. Change all your uses of timeseries functions such as iBarShift etc

               so that they are prefaced with MTF_. For example: iBarShift

               becomes MTF_iBarShift, iClose becomes MTF_iClose etc.



For an explanation of how method A works, see the notes at the end of the file.



The way in which this header file fixes the timeseries functions relies

on three current properties of MT4 when a multi-timeframe indicator

is added to an EA's backtesting chart. These might change in MT4 in future:



*  The value of TimeCurrent() which the indicator sees is the simulated 

   backtesting time, not the real live broker time. (Note: the value of Bid 

   is the live market price, not the latest backtesting price, and cannot be used)

*  The buffers which are passed to the indicator's OnCalculate event

   contain the simulated, historical data - in other words, these

   buffers do work correctly in the backtesting environment.

*  The timeseries functions such as iBarShift do give the indicator access to

   historic data up to the start of the backtesting period. The problem

   is that the historic data on other timeframes does not then update.



As an extra note: iBarShift etc do work in a multi-timeframe indicator

if the requested timeframe is the same as the backtesting timeframe.

What is clearly happening is that MT4 detects that it can use the

main backtesting data rather than timeseries access.



What this code does is maintain its own timeseries data, and then

do lookups on that instead of using the normal iBarShift, iClose etc.

It grabs the static history data which is available and correct at the start

of the backtesting (see note above), and then adds in new and updated

bars from each simulated tick in the backtesting.



One final note: if you do a backtest of an EA which is paused at start-up,

and add the indicator to the chart while the backtesting is paused,

then you will get an immediate call to OnCalculate. At this point,

no MTF data will be available. The data becomes available once you

un-pause the backtesting and actual simulated ticks start happening.



Changes, v2:



* Fixed bug with exact=true in iBarShift



Changes, v3:



* Fixed bug with W1 and MN1 (rounding bar times)

* Allowed static arrays with the CopyRates, CopyHigh etc functions

* The CopyRates, CopyHigh etc functions previously always treated

  the destination as though you had set it as-series. They now 

  respect whether or not you have done ArraySetAsSeries(..., true)

            

############################################################################################## */





// Prevent the code from maintaining huge numbers of M1 bars...

#define MTF_MAX_BARS    700000





/* ----------------------------------------------------------------------------------------------

Class which defines a timeframe such as M5, and the data required for it.

This code always collects and maintains a class for M1 data. It builds 

other timeframes as and when needed. The first time that you call a 

timeseries function such as iLowest for a particular timeframe, this code 

will initialise the corresponding member of the glbMTF_Periods array (see below).



The data for a timeframe is built up in three parts:

   (a) on first use, the Init() function grabs the data which is provided

       by the backtesting environment. This data *is* accurate, complete

       and reliable; the issue is that it doesn't then update

   (b) again on first use, the data for periods other than M1 is topped

       up from M1 (which is always collected on a continuing basis). This 

       handles scenarios where your indicator does not use some

       timeframes immediately in its operations

   (c) once active and initialised, the class updates itself with

       each new tick in the backtesting environment

       

This class implements all the timeseries functions such as iBarShift, iHighest,

iTime etc. There are then wrapper functions below such as MTF_iBarShift which

simply make calls into the relevant class in the glbMTF_Periods array.

---------------------------------------------------------------------------------------------- */



class clsMTFData {

public:

   bool IsActive;

   int Timeframe;

   MqlRates bars[]; // In *descending* order, newest in [0]

   long bartimes[]; // In *ascending* order, oldest in [0]

   int BarCount;

      

   clsMTFData() {IsActive = false; BarCount = 0; ArraySetAsSeries(bars, true);}

   

   void Init(int ForTimeframe) {

      Timeframe = ForTimeframe;



      if (!TimeCurrent()) return;

      if (IsActive) return;



      int res = CopyRates(Symbol(), ForTimeframe, 0, MathMin(MTF_MAX_BARS, TerminalInfoInteger(TERMINAL_MAXBARS)), bars);

      if (res < 0) {

         BarCount = 0;

         return;

      }



      BarCount = MathMin(res, MTF_MAX_BARS);

      IsActive = true;

      Print("MTF init: " , EnumToString((ENUM_TIMEFRAMES)ForTimeframe), ", bars available: " , BarCount);

      

      // Force the array to have the fixed size

      ArrayResize(bars, MTF_MAX_BARS);

      ArrayResize(bartimes, MTF_MAX_BARS);

      

      // If this isn't M1, then we top up with M1, in glbMTF_Periods[0], which is guaranteed

      // to exist by now 

      if (BarCount && ForTimeframe != PERIOD_M1) {

         for (int i = glbMTF_Periods[0].BarCount - 1; i >= 0; i--) {

            MqlRates m1bar = glbMTF_Periods[0].bars[i];

            if (m1bar.time < TimeCurrent()) {            

               if (m1bar.time < bars[0].time) {

                  // Keep searching

               } else {

                  // Fill in with all following M1 bars

                  for (int j = i; j >= 0; j--) {

                     MqlRates m1barcopy = glbMTF_Periods[0].bars[j];

                     UpdateWithTick(m1barcopy.time, m1barcopy.open, m1barcopy.high, m1barcopy.low, m1barcopy.close, 0);               

                  }

                  i = -1;

               }

            }

         }

      }

      IndexBars();

      glbMTF_DoneInit = true;

   }



   void UpdateWithTick(datetime tm, double open, double high, double low, double close, long volume) {

      if (!IsActive) return;

      
 if(ArraySize(bars)>0)
       {
      if (BarCount <= 0) {

         BarCount = 1;



         bars[0].time = tm;

         bars[0].open = open;

         bars[0].high = high;

         bars[0].low = low;

         bars[0].close = close;

         bars[0].tick_volume = volume;



         bartimes[0] = RoundTime(tm);

         

      } else if (RoundTime(tm) == RoundTime(bars[0].time)) {

         bars[0].low = MathMin(bars[0].low, low);

         bars[0].high = MathMax(bars[0].high, high);

         bars[0].close = close;

         bars[0].tick_volume += volume;



      } else {

         bool bTrim = false;

         if (BarCount < MTF_MAX_BARS) {

            BarCount++;

            bTrim = false;

         } else {

            bTrim = true;

         }



         // Need to shuffle the list of bars up, losing the oldest, and 

         // adding a record at the start. It appears to be safe to copy

         // the array onto itself, although https://docs.mql4.com/array/arraycopy

         // says that the behaviour of this is "undefined".

         // (Such an array copy *probably* ends up being done with the Win32

         // RtlMoveMemory() function which is safe for such overlapping use)

         ArrayCopy(bars, bars, 1, 0, BarCount - 1);   



         /* Slower version which is safe, according to the documenation:

         MqlRates temp[];

         ArraySetAsSeries(temp, true);

         ArrayResize(temp, BarCount - 1);

         ArrayCopy(temp, bars, 0, 0, BarCount - 1);

         ArrayCopy(bars, temp, 1, 0, BarCount - 1);

         */

          if(ArraySize(bars)>0)
          {
      
         bars[0].time = tm;

         bars[0].open = open;

         bars[0].high = high;

         bars[0].low = low;

         bars[0].close = close;

         bars[0].tick_volume = volume;

         }

         if (bTrim) {         

            // Need to shuffle the index down, losing the record in [0], and 

            // adding a record at the end. It appears to be safe to copy

            // the array onto itself, although https://docs.mql4.com/array/arraycopy

            // says that the behaviour of this is "undefined".

            // (Such an array copy *probably* ends up being done with the Win32

            // RtlMoveMemory() function which is safe for such overlapping use)

         

         

         //   ArrayCopy(bartimes, bartimes, 0, 1, BarCount - 1);   

            

         //    Slower version which is safe, according to the documenation:

            datetime btemp[];

            ArrayResize(btemp, BarCount - 1);

            ArrayCopy(btemp, bartimes, 0, 1, BarCount - 1);

            ArrayCopy(bartimes, btemp, 0, 0, BarCount - 1);            

            

         }
        }
         bartimes[BarCount - 1] = RoundTime(tm);

      }

   }

   

   // One-off creation of rounded-down open times for each existing bar,

   // stored in ascending order

   void IndexBars() {

      for (int i = BarCount - 1; i >= 0; i--) {
         if(ArraySize(bartimes)>0)
         {
         bartimes[BarCount - i - 1] = RoundTime(bars[i].time);
         }

      }

   }

   

   datetime RoundTime(datetime tm) {

      switch (Timeframe) {

         case PERIOD_MN1:

            return tm - (tm % 86400) - ((TimeDay(tm) - 1) * 86400);

         case PERIOD_W1:

            return tm - (tm % 86400) - (TimeDayOfWeek(tm) * 86400);

         default:

            return tm - (tm % (Timeframe * 60));

      }

   }

   

   int Get_BarShift(datetime tm, bool exact) {

      if (!BarCount) return -1;

      long rtm = (long)RoundTime(tm);

      int bidx = ArrayBsearch(bartimes, rtm, BarCount, 0);

      if (bidx < 0 || bidx >= BarCount) return -1;

      if (bartimes[bidx] != rtm) return -1;

      if (exact) {

         MqlRates b = bars[BarCount - bidx - 1];

         return (b.time == tm ? BarCount - bidx - 1 : -1);

      } else {

         return BarCount - bidx - 1;

      }

   }

   

   double Get_Close(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].close);}

   double Get_Open(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].open);}

   double Get_High(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].high);}

   double Get_Low(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].low);}

   datetime Get_Time(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].time);}

   long Get_Volume(int shift) {return (shift < 0 || shift >= BarCount ? 0 : bars[shift].tick_volume);}

   

   int Get_Highest(int type, int count = WHOLE_ARRAY, int startat = 0) {

      if (startat < 0 || startat >= BarCount) return -1;

      if (count == WHOLE_ARRAY) count = BarCount - startat + 1;

      

      double vMax = -999999999999;

      int retval = -1;

      

      for (int i = startat; i < startat + count; i++) {

         switch (type) {

            case MODE_OPEN:      if (vMax < bars[i].open) {vMax = bars[i].open; retval = i;} break;

            case MODE_HIGH:      if (vMax < bars[i].high) {vMax = bars[i].high; retval = i;} break;

            case MODE_LOW:       if (vMax < bars[i].low) {vMax = bars[i].low; retval = i;} break;

            case MODE_CLOSE:     if (vMax < bars[i].close) {vMax = bars[i].close; retval = i;} break;

            case MODE_VOLUME:    if (vMax < (double)bars[i].tick_volume) {vMax = (double)bars[i].tick_volume; retval = i;} break;

            case MODE_TIME:      if (vMax < (double)bars[i].time) {vMax = (double)bars[i].time; retval = i;} break;

         }

      }

      return retval;

   }



   int Get_Lowest(int type, int count = WHOLE_ARRAY, int startat = 0) {

      if (startat < 0 || startat >= BarCount) return -1;

      if (count == WHOLE_ARRAY) count = BarCount - startat + 1;

      

      double vMin = 999999999999;

      int retval = -1;

      

      for (int i = startat; i < startat + count; i++) {

         switch (type) {

            case MODE_OPEN:      if (vMin > bars[i].open) {vMin= bars[i].open; retval = i;} break;

            case MODE_HIGH:      if (vMin > bars[i].high) {vMin = bars[i].high; retval = i;} break;

            case MODE_LOW:       if (vMin > bars[i].low) {vMin = bars[i].low; retval = i;} break;

            case MODE_CLOSE:     if (vMin > bars[i].close) {vMin = bars[i].close; retval = i;} break;

            case MODE_VOLUME:    if (vMin > (double)bars[i].tick_volume) {vMin = (double)bars[i].tick_volume; retval = i;} break;

            case MODE_TIME:      if (vMin > (double)bars[i].time) {vMin = (double)bars[i].time; retval = i;} break;

         }

      }

      return retval;

   }

   

   int Get_CopyRates(int startat, int count, MqlRates & arr[]) {

      if (startat < 0 || startat >= BarCount) return -1;

      if (count <= 0) return -1;

      count = MathMin(count, BarCount - startat);

      

      if (ArrayIsDynamic(arr)) {

         ArrayResize(arr, count);

      } else {

         count = MathMin(count, ArraySize(arr));

         if (!count) return -1;

      }

      

      // Make sure that the destination arr, is as-series to match

      // the data we are copying in, and then restore the 

      // original as-series state

      bool bIsSeries = ArrayGetAsSeries(arr);

      ArraySetAsSeries(arr, true);

      ArrayCopy(arr, bars, 0, startat, count);

      ArraySetAsSeries(arr, bIsSeries);

      

      return count;

   }



   int Get_CopyRates(datetime starttime, int count, MqlRates & arr[]) {

      return Get_CopyRates(Get_BarShift(starttime, false), count, arr);

   }



   int Get_CopyRates(datetime starttime, datetime stoptime, MqlRates & arr[]) {

      int idxstart = Get_BarShift(starttime, false);

      int idxend = Get_BarShift(stoptime, false);

      return Get_CopyRates(idxstart, idxend - idxstart + 1, arr);

   }



   template <typename T>

   int Get_CopyValue(string datamember, int startat, int count, T & arr[]) {

      if (startat < 0 || startat >= BarCount) return -1;

      if (count <= 0) return -1;

      count = MathMin(count, BarCount - startat);



      if (ArrayIsDynamic(arr)) {

         ArrayResize(arr, count);

      } else {

         count = MathMin(count, ArraySize(arr));

         if (!count) return -1;

      }



      // Make sure that the destination arr, is as-series to match

      // the data we are copying in, and then restore the 

      // original as-series state

      bool bIsSeries = ArrayGetAsSeries(arr);

      ArraySetAsSeries(arr, true);

      

      for (int i = startat; i < startat + count; i++) {

         if (datamember == "open") {

            arr[i - startat] = (T)bars[i].open;

         } else if (datamember == "high") {

            arr[i - startat] = (T)bars[i].high;

         } else if (datamember == "low") {

            arr[i - startat] = (T)bars[i].low;

         } else if (datamember == "close") {

            arr[i - startat] = (T)bars[i].close;

         } else if (datamember == "time") {

            arr[i - startat] = (T)bars[i].time;

         } else if (datamember == "volume") {

            arr[i - startat] = (T)bars[i].tick_volume;

         }

      }



      // Restore original as-series state

      ArraySetAsSeries(arr, bIsSeries);



      return count;

   }



   template <typename T>

   int Get_CopyValue(string datamember, datetime starttime, int count, T & arr[]) {

      return Get_CopyValue(datamember, Get_BarShift(starttime, false), count, arr);

   }



   template <typename T>

   int Get_CopyValue(string datamember, datetime starttime, datetime stoptime, T & arr[]) {

      int idxstart = Get_BarShift(starttime, false);

      int idxend = Get_BarShift(stoptime, false);

      return Get_CopyValue(datamember, idxstart, idxend - idxstart + 1, arr);

   }

};





/* ----------------------------------------------------------------------------------------------

Global variables

---------------------------------------------------------------------------------------------- */



// Indicates whether the MTF data is initialised

bool glbMTF_DoneInit = false;



// Instances of the clsMTFData class for each timeframe in MT4: M1, M5 etc

clsMTFData glbMTF_Periods[9];



// Time and price of most recent tick

datetime glbMTF_LatestTickTime;

double glbMTF_LatestTickPrice;





/* ----------------------------------------------------------------------------------------------

Function which determines whether we can use the normal timeseries functions, or

whether we need to maintain and use internal data. If the indicator is being used

on a normal non-backtesting chart, then it ends up using the normal timeseries

functions (with an utterly trivial speed overhead).

---------------------------------------------------------------------------------------------- */



bool MTF_UseInternal(int tf)

{

   #ifdef FORCE_INTERNAL_MTF

      return true;

   #else

      if (IsTesting()) {

         // In the strategy tester. If the period requested is the same

         // as the period being backtested, then it *is* safe to

         // use the normal timeseries functions. If not, then that's

         // where we have a problem, and where we have to switch

         // to using the internal data

         if (tf == 0 || tf == Period()) {

            // Period being requested in timeseries function is

            // same as backtesting period. Can use standard

            // timeseries functions

            return false;

         } else {

            // Need to use internal timeseries data

            return true;

         }

      } else {

         // Not in strategy tester. Can use standard timeseries functions

         return false;

      }

   #endif

}



/* ----------------------------------------------------------------------------------------------

Translates from a timeframe such as H4 to the corresponding index in the array

of clsMTFData classes

---------------------------------------------------------------------------------------------- */



int MTF_MapTimeframeToArrayIndex(int ForTimeframe)

{

   // Map TF to index into glbMTF_Periods

   switch (ForTimeframe) {

      case PERIOD_M1:   return 0;

      case PERIOD_M5:   return 1;

      case PERIOD_M15:  return 2;

      case PERIOD_M30:  return 3;

      case PERIOD_H1:   return 4;

      case PERIOD_H4:   return 5;

      case PERIOD_D1:   return 6;

      case PERIOD_W1:   return 7;

      case PERIOD_MN1:  return 8;      

      default:

         Alert("UNRECOGNISED TIMEFRAME: " , ForTimeframe);

         return 0;     

   }

}



/* ----------------------------------------------------------------------------------------------

Function which causes the data to be built for a period such as M5.  

---------------------------------------------------------------------------------------------- */

void MTF_BuildData(int ForTimeframe)

{

   if (!glbMTF_DoneInit) {

      glbMTF_Periods[0].Init(PERIOD_M1);

      glbMTF_DoneInit = true;

      Print("Using internal MTF data");

   }

   

   int idx = MTF_MapTimeframeToArrayIndex(ForTimeframe);

   glbMTF_Periods[idx].Init(ForTimeframe);

}



/* ----------------------------------------------------------------------------------------------

Function which updates all active time periods with the latest tick

---------------------------------------------------------------------------------------------- */

void MTF_UpdateWithLatestTick(datetime tm, double price)

{

   if (tm == 0) return;



   #ifdef FORCE_INTERNAL_MTF

      // Always process

   #else

      // Don't need to do anything if we're not in backtesting

      if (!IsTesting()) return;

   #endif

   

   // Make sure that we're collecting M1 data

   MTF_BuildData(PERIOD_M1);

   

   if (glbMTF_LatestTickTime != TimeCurrent() || glbMTF_LatestTickPrice != price) {

      for (int i = 0; i < 9; i++) {

         if (glbMTF_Periods[i].IsActive) glbMTF_Periods[i].UpdateWithTick(tm, price, price, price, price, 1);

      }   

      glbMTF_LatestTickPrice = price;

      glbMTF_LatestTickTime = tm;

   }

}



/* ----------------------------------------------------------------------------------------------

Series of wrappers around each timeseries function such as iBarShift. These all use

the MTF_UseInternal function above to determine whether it is safe to use the

normal timeseries functions. If not, then they simply make a call into 

the corresponding clsMTFData class, which contains the actual functionality for

the timeseries function.

---------------------------------------------------------------------------------------------- */



int MTF_iBarShift(string symbol, int tf, datetime bartime, bool exact = false)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_BarShift(bartime, exact);

   } else {

      return iBarShift(symbol, tf, bartime, exact);

   }  

}



double MTF_iClose(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Close(shift);

   } else {

      return iClose(symbol, tf, shift);

   }  

}



double MTF_iHigh(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_High(shift);

   } else {

      return iHigh(symbol, tf, shift);

   }  

}



double MTF_iLow(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Low(shift);

   } else {

      return iLow(symbol, tf, shift);

   }  

}



double MTF_iOpen(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Open(shift);

   } else {

      return iOpen(symbol, tf, shift);

   }  

}



datetime MTF_iTime(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Time(shift);

   } else {

      return iTime(symbol, tf, shift);

   }  

}



long MTF_iVolume(string symbol, int tf, int shift)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Volume(shift);

   } else {

      return iVolume(symbol, tf, shift);

   }  

}



int MTF_iHighest(string symbol, int tf, int type, int count = WHOLE_ARRAY, int startat = 0)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Highest(type, count, startat);

   } else {

      return iHighest(symbol, tf, type, count, startat);

   }  

}



int MTF_iLowest(string symbol, int tf, int type, int count = WHOLE_ARRAY, int startat = 0)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_Lowest(type, count, startat);

   } else {

      return iLowest(symbol, tf, type, count, startat);

   }  

}



int MTF_iBars(string symbol, int tf)

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].BarCount;

   } else {

      return iBars(symbol, tf);

   }  

}



int MTF_CopyRates(string symbol, int tf, int startat, int count, MqlRates & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyRates(startat, count, arr);

   } else {

      return CopyRates(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyRates(string symbol, int tf, datetime starttime, int count, MqlRates & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyRates(starttime, count, arr);

   } else {

      return CopyRates(symbol, tf, starttime, count, arr);

   }  

}





int MTF_CopyRates(string symbol, int tf, datetime starttime, datetime stoptime, MqlRates & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyRates(starttime, stoptime, arr);

   } else {

      return CopyRates(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyOpen(string symbol, int tf, int startat, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("open", startat, count, arr);

   } else {

      return CopyOpen(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyHigh(string symbol, int tf, int startat, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("high", startat, count, arr);

   } else {

      return CopyHigh(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyLow(string symbol, int tf, int startat, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("low", startat, count, arr);

   } else {

      return CopyLow(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyClose(string symbol, int tf, int startat, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("close", startat, count, arr);

   } else {

      return CopyClose(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyTime(string symbol, int tf, int startat, int count, datetime & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("time", startat, count, arr);

   } else {

      return CopyTime(symbol, tf, startat, count, arr);

   }  

}



int MTF_CopyTickVolume(string symbol, int tf, int startat, int count, long & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("volume", startat, count, arr);

   } else {

      return CopyTickVolume(symbol, tf, startat, count, arr);

   }  

}



   

int MTF_CopyOpen(string symbol, int tf, datetime starttime, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("open", starttime, count, arr);

   } else {

      return CopyOpen(symbol, tf, starttime, count, arr);

   }  

}



int MTF_CopyHigh(string symbol, int tf, datetime starttime, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("high", starttime, count, arr);

   } else {

      return CopyHigh(symbol, tf, starttime, count, arr);

   }  

}



int MTF_CopyLow(string symbol, int tf, datetime starttime, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("low", starttime, count, arr);

   } else {

      return CopyLow(symbol, tf, starttime, count, arr);

   }  

}



int MTF_CopyClose(string symbol, int tf, datetime starttime, int count, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("close", starttime, count, arr);

   } else {

      return CopyClose(symbol, tf, starttime, count, arr);

   }  

}



int MTF_CopyTime(string symbol, int tf, datetime starttime, int count, datetime & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("time", starttime, count, arr);

   } else {

      return CopyTime(symbol, tf, starttime, count, arr);

   }  

}



int MTF_CopyTickVolume(string symbol, int tf, datetime starttime, int count, long & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("volume", starttime, count, arr);

   } else {

      return CopyTickVolume(symbol, tf, starttime, count, arr);

   }  

}

   



int MTF_CopyOpen(string symbol, int tf, datetime starttime, datetime stoptime, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("open", starttime, stoptime, arr);

   } else {

      return CopyOpen(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyHigh(string symbol, int tf, datetime starttime, datetime stoptime, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("high", starttime, stoptime, arr);

   } else {

      return CopyHigh(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyLow(string symbol, int tf, datetime starttime, datetime stoptime, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("low", starttime, stoptime, arr);

   } else {

      return CopyLow(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyClose(string symbol, int tf, datetime starttime, datetime stoptime, double & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("close", starttime, stoptime, arr);

   } else {

      return CopyClose(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyTime(string symbol, int tf, datetime starttime, datetime stoptime, datetime & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("time", starttime, stoptime, arr);

   } else {

      return CopyTime(symbol, tf, starttime, stoptime, arr);

   }  

}



int MTF_CopyTickVolume(string symbol, int tf, datetime starttime, datetime stoptime, long & arr[])

{

   if (MTF_UseInternal(tf)) {

      if (!tf) tf = Period();

      MTF_BuildData(tf);

      return glbMTF_Periods[MTF_MapTimeframeToArrayIndex(tf)].Get_CopyValue("volume", starttime, stoptime, arr);

   } else {

      return CopyTickVolume(symbol, tf, starttime, stoptime, arr);

   }  

}







/* ----------------------------------------------------------------------------------------------

The "injection" into your indicator, described at the top of this file, used unless

you turn it off by doing  #define MTF_NO_REPLACEMENTS



This consists of two parts, equivalent to steps 2 and 3 in the alternative manual process

described above. Part 1 is a series of #define directives which turn your uses of iBarShift

into MTF_iBarShift etc (without affecting the code above). Part 2 is a #define which

renames your own OnCalculate event to Override_OnCalculate, and declares an OnCalculate 

in this code which then calls your actual OnCalculate.



---------------------------------------------------------------------------------------------- */



#ifndef MTF_NO_REPLACEMENTS



#define iBarShift          MTF_iBarShift

#define iClose             MTF_iClose

#define iOpen              MTF_iOpen

#define iHigh              MTF_iHigh

#define iLow               MTF_iLow

#define iTime              MTF_iTime

#define iVolume            MTF_iVolume

#define iHighest           MTF_iHighest

#define iLowest            MTF_iLowest

#define iBars              MTF_iBars

#define CopyRates          MTF_CopyRates

#define CopyOpen           MTF_CopyOpen

#define CopyHigh           MTF_CopyHigh

#define CopyLow            MTF_CopyLow

#define CopyClose          MTF_CopyClose

#define CopyTime           MTF_CopyTime

#define CopyTickVolume     MTF_CopyTickVolume



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[])

{

   MTF_UpdateWithLatestTick(TimeCurrent(), close[0]);

   return Override_OnCalculate(rates_total, prev_calculated, time, open, high, low, close, tick_volume, volume, spread);

}

#define OnCalculate  Override_OnCalculate



#endif



