Little HELP with Indicator Class

 

Wishing everyone a great weekend.

I am working on an EA with OOP and trying to create indicators for the same.

My object is to standardize calculations of my indicators (in CLASSes) and prefer to use averaging methods from <MovingAverages.mqh> file.

In my attempt:

1) able to create CiVolume.mq5 indicator which returns the correct values. https://www.mql5.com/en/articles/10

2) able to create Indicator_cBase.mqh, Indicator_cVolume.mqh and cVolume.mq5. However with this approach I am not getting the correct values. (Tutorials from https://orchardforex.com/)

The code files are attached here, and seeking masters guidance to complete my indicator calculations in the Class, instead of using iCustom.

Thanks in advance.

CiVolume.mq5 ... The code for working indicator

  #property description       "Normalized Volume Indicator"
  #property description       "With selection of MA Method / Volume Type defaulted to TickVolume"
  #property indicator_separate_window
  #property indicator_buffers 3
  #property indicator_plots   1
  #property indicator_color1  clrOrangeRed,clrSeaGreen,clrYellowGreen
  #include <MovingAverages.mqh>
//+----------------------------------------------------------------------------------------------------------+
  input int             InpAvgPeriod = 13;       // Averaging Period
  input int             InpThreshold = 200;      // 
  input ENUM_MA_METHOD  InpMAMethod  = MODE_LWMA;
//+----------------------------------------------------------------------------------------------------------+
  int       ExtAvgPeriod, ExtThreshold;
//+----------------------------------------------------------------------------------------------------------+
  double    Buffer_NormVOL[];
  double    Buffer_ColorIndex[];
  double    Buffer_Volume[];
//+------------------------------------------------------------------+
int OnInit()
  {
  //+--------------------------------------------------------------------------------------------------------+
    if(InpAvgPeriod < 2)
      {
        ExtAvgPeriod = 13;
        PrintFormat("%s: InpAvgPeriod %d is too low. Recommended period %d will be used.",
                   (string)__FILE__,InpAvgPeriod,ExtAvgPeriod);
      }
    else
      ExtAvgPeriod = InpAvgPeriod;
    if(InpThreshold < 100)
      {
        ExtThreshold = 100;
        PrintFormat("%s: InpThreshold %d is too low. Recommended threshold %d will be used.",
                   (string)__FILE__,InpAvgPeriod,ExtThreshold);
      }
    else
      ExtThreshold = InpThreshold;
  //+--------------------------------------------------------------------------------------------------------+
    IndicatorSetString(INDICATOR_SHORTNAME,"AKT Volume% ("+(string)ExtAvgPeriod+")  "+EnumToString(InpMAMethod)+" ");
    IndicatorSetInteger(INDICATOR_DIGITS,0);
    IndicatorSetInteger(INDICATOR_HEIGHT,100);
    IndicatorSetDouble(INDICATOR_MINIMUM,0.0);
    IndicatorSetInteger(INDICATOR_LEVELS,1);
    IndicatorSetDouble(INDICATOR_LEVELVALUE,0,ExtThreshold);
  //+--------------------------------------------------------------------------------------------------------+
    SetIndexBuffer(0,Buffer_NormVOL,INDICATOR_DATA);
    SetIndexBuffer(1,Buffer_ColorIndex,INDICATOR_COLOR_INDEX);
    SetIndexBuffer(2,Buffer_Volume,INDICATOR_DATA);
  //+--------------------------------------------------------------------------------------------------------+
    PlotIndexSetString(0,PLOT_LABEL,"VOL% ("+string(ExtAvgPeriod)+") ");
    PlotIndexSetInteger(0,PLOT_DRAW_TYPE,DRAW_COLOR_HISTOGRAM);
    PlotIndexSetInteger(0,PLOT_LINE_STYLE,STYLE_SOLID);
    PlotIndexSetInteger(0,PLOT_LINE_WIDTH,2);
    PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  //--- indexes draw begin settings
    PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,ExtAvgPeriod-1);
  //---
     return(0);
  } // End Of method OnInit()
//+----------------------------------------------------------------------------------------------------------+
int OnCalculate(const int       rates_total,        // size of the price[] array;
                const int       prev_calculated,    // number of available bars;
                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[])

  {
  //--- if the size of bars on chart is too small
    if(rates_total < ExtAvgPeriod) return(0); // do not calculate or draw anything
  //--- if it's the first call
    if(prev_calculated == 0)
      {
        Buffer_Volume[0] = (double)tick_volume[0];  // Set bar open Volume at Index[0]
      }
  //--- calculate values of mtm and |mtm|
    int start;
    if(prev_calculated == 0)
      start = 1;                                    // start filling out Buffer_Volume[] from the 1st index
    else
      start = prev_calculated-1;                    // set start equal to the last index in the arrays
    for(int i = start; i < rates_total && !IsStopped(); i++)
      {
        Buffer_Volume[i] = (double)tick_volume[i];
      }
  //--- calculate the moving average on arrays
    switch(InpMAMethod)
      {
        case MODE_EMA:
          ExponentialMAOnBuffer(rates_total,prev_calculated,
                                1,                // index, starting from which data for smoothing are available
                                ExtAvgPeriod,     // period of the exponential average
                                Buffer_Volume,    // buffer to calculate average
                                Buffer_NormVOL);  // into this buffer locate value of the average
          break;
        case MODE_LWMA:
          LinearWeightedMAOnBuffer(rates_total,prev_calculated,1,ExtAvgPeriod,Buffer_Volume,Buffer_NormVOL);
          break;
        default:                                  // none of above then apply default method
          SimpleMAOnBuffer(rates_total,prev_calculated,1,ExtAvgPeriod,Buffer_Volume,Buffer_NormVOL);
          break;
      }
  //--- Set value for Color Index of the indicator
    for(int i = start; i < rates_total && !IsStopped(); i++)
      {
        if(Buffer_NormVOL[i] < ExtThreshold)
          Buffer_ColorIndex[i] = 0;
        else if(Buffer_NormVOL[i] >= (2*ExtThreshold))
          Buffer_ColorIndex[i] = 2;
        else
          Buffer_ColorIndex[i] = 1;
      }
  //--- return value of prev_calculated for next call
     return(rates_total);
  } // End Of method OnCalculate()
//+----------------------------------------------------------------------------------------------------------+

 Indicator Class Code Files

//+----------------------------------------------------------------------------------------------------------+
class CIndicator_cBase {

private:

protected:
  //--- defined at Child Class Init()
    string          mSymbol;
    ENUM_TIMEFRAMES mTimeFrame;
    double          mBuffer[];
    double          mSmoothingMultiplier;   // Smoothing multiplier for ExponentialMA

    virtual void    Update();
    virtual void    UpdateValues(int pBars,int pLimit);

    int             mPrevCalculated;  // to handle prev_calculated bars of the indicator
    datetime        mFirstBarTime;    // to capture, if bar changed on chart
  //--- called by Child Class for further calculation
    long            Volume(const int pIndex);

public:
    CIndicator_cBase();               // default constructor
   ~CIndicator_cBase() {};            // default deconstructor no code needed (Body not defined)
  //--- methods to get values for a range or specified index
            void    GetArray(const int pStart,const int pCount,double &pArray[]);
    virtual double  GetValue(const int pIndex);
};
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  CIndicator_cBase
//| APPLICATION:  default constructor
//+----------------------------------------------------------------------------------------------------------+
CIndicator_cBase::CIndicator_cBase() {
    mSmoothingMultiplier = 0.0;     // initialize Zero and set in Child Init()
  //---
    mFirstBarTime   = 0.0;
    mPrevCalculated = 0.0;
  //--- Manually manage BufferArrays
    ArraySetAsSeries(mBuffer,true);
} // End of method Init()
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  Update
//| APPLICATION:  Same task as OnCalculate function in the indicator
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cBase::Update(void) {
    int      vBars         = iBars(mSymbol,mTimeFrame);           // How many bars available for calculation
    datetime vFirstBarTime = iTime(mSymbol,mTimeFrame,vBars-1);   // Time of first available bar
  //--- How many bars must be calculated
    int      vLimit        = vBars - mPrevCalculated; // How many bars we have NOT calculated)
    if(mPrevCalculated > 0) vLimit++;                 // This forces to recalculate current bar i.e. Index[0]
    if(vFirstBarTime != mFirstBarTime) {
      vLimit        = vBars;                          // FirstBar time change means, recalculate everything
      mFirstBarTime = vFirstBarTime;                  // Just a reset to member variable
    }
    if(vLimit <= 0) return;                           // Should not happen but better to be safe
  //--- Manually manage the mBuffer array size
    if(vBars != ArraySize(mBuffer)) {
      ArrayResize(mBuffer,vBars);                     // resize equal to vBars (i.e. rates_total)
    }
    UpdateValues(vBars,vLimit);
} // End of method Update()
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  UpdateValues()
//| APPLICATION:  
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cBase::UpdateValues(int pBars,int pLimit) {
    mPrevCalculated = pBars;
    return;
} // End of method UpdateValues()
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  Volume()
//| APPLICATION:  
//+----------------------------------------------------------------------------------------------------------+
long CIndicator_cBase::Volume(const int pIndex) {
    long result = iVolume(mSymbol,mTimeFrame,pIndex);
    return(result);
} // End of method Volume()
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  GetArray()
//| APPLICATION:  
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cBase::GetArray(const int pStart,const int pCount,double &pArray[]) {
  //--- Reset input array size and as series = true
    ArrayResize(pArray,pCount);
    ArraySetAsSeries(pArray,true);
    for(int i = pStart; i < pCount; i++) {
      pArray[i] = GetValue(i);
    }
} // End of method GetArray()
//+----------------------------------------------------------------------------------------------------------+
//| METHOD BODY:  GetValue()
//| APPLICATION:  
//+----------------------------------------------------------------------------------------------------------+
double CIndicator_cBase::GetValue(const int pIndex) {
    double result = 0.0;
  //---
    if(pIndex >= ArraySize(mBuffer)) {
      PrintFormat("%s: Requested index[%i] is higher than Max Available [%i]. Zero value is returned",
                 (string)__FUNCTION__,pIndex,ArraySize(mBuffer));
    }
    result = mBuffer[pIndex];
  return(result);
} // End of method GetValue()
//+----------------------------------------------------------------------------------------------------------+
  #include  "Indicator_cBase.mqh"
  #include  <MovingAverages.mqh>
//+----------------------------------------------------------------------------------------------------------+
class CIndicator_cVolume : public CIndicator_cBase {    // class inheritance from CIndicator_cBase

private:
    int             mMAPeriod;              // Normalize Volume Average Period
    ENUM_MA_METHOD  mMAMethod;              // Averaging Method
    double          mTemp[];
protected:
    virtual void    UpdateValues(int pBars,int pLimit);

public:
    CIndicator_cVolume() {};                            // default constructor, body not defined
    CIndicator_cVolume(string pSymbol,ENUM_TIMEFRAMES pTimeFrame,int pMAPeriod,ENUM_MA_METHOD pMAMethod);
   ~CIndicator_cVolume() {};                            // default deconstructor, body not defined
            void    Init(string pSymbol,ENUM_TIMEFRAMES pTimeFrame,int pMAPeriod,ENUM_MA_METHOD pMAMethod);
}; // END Of class definition
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cVolume::CIndicator_cVolume(string pSymbol,ENUM_TIMEFRAMES pTimeFrame,int pMAPeriod,
                                            ENUM_MA_METHOD pMAMethod) {
    Init(pSymbol,pTimeFrame,pMAPeriod,pMAMethod);
} // End of alternate constructor CIndicator_cVolume()
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cVolume::Init(string pSymbol,ENUM_TIMEFRAMES pTimeFrame,int pMAPeriod,
                              ENUM_MA_METHOD pMAMethod) {
    mSymbol              = pSymbol;
    mTimeFrame           = pTimeFrame;
    mMAPeriod            = pMAPeriod;
    mMAMethod            = pMAMethod;
    mSmoothingMultiplier = 2.0/(mMAPeriod + 1.0);
  //---
    Update();                               // First time call to Update() of Base Class
  //---
    //ArraySetAsSeries(mTemp,true);         // AsSeries=true 'or' false did not change values!!!
} // End of method Init()
//+----------------------------------------------------------------------------------------------------------+
void CIndicator_cVolume::UpdateValues(int pBars,int pLimit) {
  //---
    if(pBars > ArraySize(mTemp))
      ArrayResize(mTemp,pBars);
  //--- if it's the first call
    if(mPrevCalculated == 0)
      mTemp[0] = (double)Volume(0);                 // Set bar open Volume at Index[0]
  //--- store volume values
    int start;
    if(mPrevCalculated == 0)
      start = 1;                                    // start filling out mTemp[] from the 1st index
    else
      start = mPrevCalculated-1;                    // set start equal to the last index in the arrays
    for(int i = start; i < pBars && !IsStopped(); i++) {
      mTemp[i] = (double)Volume(i);
    }
  //---
    switch(mMAMethod) {
    case MODE_EMA:
      ExponentialMAOnBuffer(pBars,mPrevCalculated,1,  // index, starting from which data for smoothing are available
                            mMAPeriod,   // period of the exponential average
                            mTemp,          // data from mTemp used to calculate average
                            mBuffer);       // mBuffer to hold values of the average
      break;
    case MODE_LWMA:
      LinearWeightedMAOnBuffer(pBars,mPrevCalculated,1,mMAPeriod,mTemp,mBuffer);
      break;
    default:                                  // none of above then apply default method
      SimpleMAOnBuffer(pBars,mPrevCalculated,1,mMAPeriod,mTemp,mBuffer);
      break;
    }
  //---
  mPrevCalculated = pBars;
} // End of method UpdatePrevCalculated()
//+----------------------------------------------------------------------------------------------------------+
//+----------------------------------------------------------------------------------------------------------+
//| Indicator: cVolume.mq5
  #property description       "Normaliazed Volume [Vol%]"
  #property indicator_separate_window
  #property indicator_buffers 1
  #property indicator_plots   1
  #include  "\..\..\Experts\AKT\Indicator\Indicator_cVolume.mqh"
  CIndicator_cVolume        Volume;

  input int                 InpMAPeriod = 13;           // Averaging Period
  input ENUM_MA_METHOD      InpMAMethod = MODE_LWMA;
  double  BufferVolume[];
  int     bufferNo = 0;  // buffer index for mapping and ploting the indicator
//+----------------------------------------------------------------------------------------------------------+
int OnInit() {
  //+--------------------------------------------------------------------------------------------------------+
    IndicatorSetString(INDICATOR_SHORTNAME,"AKT Volume% ("+(string)InpMAPeriod+") ");
    IndicatorSetInteger(INDICATOR_DIGITS,0);
    //IndicatorSetDouble(INDICATOR_MINIMUM,0.0);
    IndicatorSetInteger(INDICATOR_HEIGHT,100);
  //+--------------------------------------------------------------------------------------------------------+
    SetIndexBuffer(bufferNo,BufferVolume,INDICATOR_DATA);
    ArraySetAsSeries(BufferVolume,true);
  //+--------------------------------------------------------------------------------------------------------+
    PlotIndexSetString(bufferNo,PLOT_LABEL,"Vol% ("+(string)InpMAPeriod+")");
    PlotIndexSetInteger(bufferNo,PLOT_DRAW_TYPE,DRAW_HISTOGRAM);
    PlotIndexSetInteger(bufferNo,PLOT_LINE_COLOR,clrSilver);
    PlotIndexSetInteger(bufferNo,PLOT_LINE_STYLE,STYLE_SOLID);
    PlotIndexSetInteger(bufferNo,PLOT_LINE_WIDTH,2);
    PlotIndexSetDouble(bufferNo,PLOT_EMPTY_VALUE,EMPTY_VALUE);
    PlotIndexSetInteger(bufferNo,PLOT_DRAW_BEGIN,InpMAPeriod-1);
  //---
    Volume.Init(_Symbol,PERIOD_CURRENT,InpMAPeriod,InpMAMethod);
  //---
    return(INIT_SUCCEEDED);
} // END of OnInit()
//+----------------------------------------------------------------------------------------------------------+
//| Indicator calculation
//| Nothing calculated in this section, instead of values taken from Indicator Class Indicator_cVolume.mqh
//+----------------------------------------------------------------------------------------------------------+
int OnCalculate(const int        rates_total,       // price[] array size
                const int        prev_calculated,   // number of handled bars at the previous call
                const int        begin,             // index number in the price[] array meaningful data starts from
                const double&    price[]) {         // array of values for calculation
  //---
    int limit = rates_total - prev_calculated;
    if(prev_calculated > 0)
      limit++;
  //---
    for(int i = limit-1; i >= 0; i--) {
      BufferVolume[i] = Volume.GetValue(i);         // Custom Indicator, have no buffer
      //PrintFormat("[%i] %.1f",i,BufferVolume[i]);
    }
  //---
    return(rates_total);
}
//+----------------------------------------------------------------------------------------------------------+

Screen shot of Indicators on the Chart

Showing both indicators drawn on chart

MQL5: Create Your Own Indicator
MQL5: Create Your Own Indicator
  • www.mql5.com
What is an indicator? It is a set of calculated values that we want to be displayed on the screen in a convenient way. Sets of values are represented in programs as arrays. Thus, creation of an indicator means writing an algorithm that handles some arrays (price arrays) and records results of handling to other arrays (indicator values). By describing creation of True Strength Index, the author shows how to write indicators in MQL5.
 
Why don't you want to use iCustom?
 
Alexandre Borela #:
Why don't you want to use iCustom?

Hi Alexandre

I have been using iCustom upto now. However I liked the idea of creating Class(s) and move my calculation into subClasses. Creating indicators always gives me hard time more specially where the averaging is required. Indicators which I download from Market, all uses different ways of calculating averages, that makes my work more complicated, as I found that most of the time I cann't duplicate the same method for another indicator.

All this makes me to think of having Standard Calculations that can be repeated for other indicators too. I found that <MovingAverages.mqh> methods are much simpler to use.

My confusion goes mainly in the loop to fetch values and return calculated values.

Hope this clarifies your question, and also it is just a personal preference too.

 
Anil Varma -:
   

In my attempt:

1) able to create CiVolume.mq5 indicator which returns the correct values. https://www.mql5.com/en/articles/10

2) able to create Indicator_cBase.mqh, Indicator_cVolume.mqh and cVolume.mq5. However with this approach I am not getting the correct values. (Tutorials from https://orchardforex.com/)

The code files are attached here, and seeking masters guidance to complete my indicator calculations in the Class

actually, if you take someone's code -- your questions are to be addressed personally to the author... I doubt, that somebody will spend time to look the tutorial video instead of you in order to refactor or check all your project... but knowing this tutorial resource I doubt that the author haven't already explained everything!...

(it's better return to the source! & if questions still remains -  try to shorten the questionable point of the whole code - e.g. with a shorter compilable example of the problem reflection - and try to ask the author)

 
Anil Varma -:

However with this approach I am not getting the correct values.

Maybe I am misreading something but I didn't get an idea about what is the issue here.

 
lippmaje #:

Maybe I am misreading something but I didn't get an idea about what is the issue here.

Hi Lippmaje

if you look at jpeg file it has same indicator displayed twice.

the first one with green bars uses CiVolume.mq5 file and works correctly without any problem.

I have tried to use the same code logic in a Indicator_cBase.mqh class file and tried to create indicator from the retuned value of the class. This one is second indicator on chart with Silver Bars.

However I am getting different values in indicator from the Class. To my understanding 

void CIndicator_cVolume::UpdateValues(int pBars,int pLimit)

seems to be the reason for this, as I am not sure if my for..loop is correct to fetch bars. If someone can help me on that, it will be great support to me.

Thanks 

 
JeeyCi #:

actually, if you take someone's code -- your questions are to be addressed personally to the author... I doubt, that somebody will spend time to look the tutorial video instead of you in order to refactor or check all your project... but knowing this tutorial resource I doubt that the author haven't already explained everything!...

(it's better return to the source! & if questions still remains -  try to shorten the questionable point of the whole code - e.g. with a shorter compilable example of the problem reflection - and try to ask the author)

Hi JeeCi

The problem is most of them dont reply. So you are left with no option but to struggle and find yourself the solution 'or' just give up and leave it.

Thanks for your response.

 

Ok I didn't go through all the code but here's my issues:

  • Update() is being called only once during initialisation.
  • mBuffer has series flag set, while mTemp has not.
 
Anil Varma - #:
 I am not sure if my for..loop is correct to fetch bars.

if you mean this part of your code

    if(mPrevCalculated == 0)
      start = 1;                                    // start filling out mTemp[] from the 1st index
    else
      start = mPrevCalculated-1;                    // set start equal to the last index in the arrays
    for(int i = start; i < pBars && !IsStopped(); i++) {
      mTemp[i] = (double)Volume(i);

perhaps try to see this

- I do not see the use of  pLimit in your member-method & the declaration of pBars & its source... (at a glance)...

but I still prefer shorter compilable example of the problem or at least attached source files - instead of such listings... of course shorter representation of the problem is better (it might help you even without us))...

p.s. though I use mq4 (therefore not promising to help you)

Range not behaving as expected
Range not behaving as expected
  • 2016.05.11
  • www.mql5.com
Goodmorning all...
 
  #include  "\..\..\Experts\AKT\Indicator\Indicator_cVolume.mqh"

BTW all .mqh-files are header files & should be put in Include directory to be automatically found by .mq4(5)-src:

#include <Indicator_cVolume.mqh>

Please help why Custom Indicator and Class values differ, though used the same calculation !!! (Reposted)
Please help why Custom Indicator and Class values differ, though used the same calculation !!! (Reposted)
  • 2021.05.12
  • www.mql5.com
NOTE: REPOSTED, as earlier one Deleted by mistake (and dont know how to undo that) Dear Forum Members I have tough time to locate why my Class Obje...
 
JeeyCi #:

BTW all .mqh-files are header files & should be put in Include directory to be automatically found by .mq4(5)-src:

#include <Indicator_cVolume.mqh>

Hi JeeyCi

thanks for your response. Yes I am aware of this path requirements for automatically locating the files.

However, I am trying to keep my files into single Folder/SubFolder under MQL\Expert. This way it is easy for me to copy all related files in one go. I don't mind to specify path and locations which I take care in my include files.

There is yet another simpler solution was given by Orchard Forex, to create ABCXYZ.mqh file where you define all the include statements and just include this file into to your main EA to pull in all files.

Regards.

Reason: