Help improving performance of Hull MA function

 

Wanting to include the Hull MA in an EA, I tried using the iCustom() call using the attached HMA indicator. This way it slows down the backtesting process very noticeably. Then I decided to adapt the indicator to create a function but the performance does not improve much. This function is called 4 times on each tick because the EA evaluates the crossover.

If possible, Does anyone have any ideas on how to improve the performance using this HMA function? Thanks in advance.

input int HMAperiod_slow_1 = 21;	// Period HMA slow 1
input int HMAperiod_slow_2 = 48;	// Period HMA slow 2

double HMA( ENUM_TIMEFRAMES _tf, int _period, int _shift )
{
        int _index = 0;
        int p = (int)MathSqrt(_period);
        int _arraySize = HMAperiod_slow_1 * HMAperiod_slow_2;
        double vector[];
        double ExtMapBuffer[];
        
        if ( Bars < _arraySize ) Alert("Not enough bars in the chart. Minimum expected is ",_arraySize );      

        ArrayResize( vector, _arraySize );
        ArraySetAsSeries(vector,true);
        
        ArrayResize( ExtMapBuffer, _arraySize );
        ArraySetAsSeries(ExtMapBuffer,true);

        
        for( _index = _arraySize-1; _index >= 0; _index-- )
                vector[_index]= 2 * iMA( NULL, _tf, _period/2, 0, MODE_LWMA, PRICE_TYPICAL, _index ) - iMA( NULL, _tf, _period,   0, MODE_LWMA, PRICE_TYPICAL, _index );

        for( _index = 0; _index < _arraySize - _period; _index++)
                ExtMapBuffer[_index] = iMAOnArray( vector,0, p, 0, MODE_LWMA, _index );

        return( ExtMapBuffer[_shift] );
}
Files:
HMA_.mq4  8 kb
 

You could read out the iMA values one by one into two buffers, say IMA_P2 (for period/2) and IMA_P1. Upon initialisation it's quite a big chunk, but later on it will be only one additional value that needs to be added to the arrays for every time frame switch.

Question remains, how to detect a time frame switch according to _tf (assuming for simplicity that _tf is always the same.) MQL5 has BarsCalculated(ind_handle) but in MQL4 there's only IndicatorCounted(), and I think that one works inside indicators only.

However once you got the buffers, the first For loop - which is apparently the workhorse - should be much faster:

for( _index = _arraySize-1; _index >= 0; _index-- )
     vector[_index]= 2 * IMA_P2[_index] - IMA_P1[_index];
 
lippmaje:

You could read out the iMA values one by one into two buffers, say IMA_P2 (for period/2) and IMA_P1. Upon initialisation it's quite a big chunk, but later on it will be only one additional value that needs to be added to the arrays for every time frame switch.

Question remains, how to detect a time frame switch according to _tf (assuming for simplicity that _tf is always the same.) MQL5 has BarsCalculated(ind_handle) but in MQL4 there's only IndicatorCounted(), and I think that one works inside indicators only.

However once you got the buffers, the first For loop - which is apparently the workhorse - should be much faster:

Thanks for the reply.

I am not completely sure what do you mean because everytime I call the HMA function it would calculate everything again.

I can think of one way to calculate the HMA when there is a new bar by evaluating when a new bar opens using iTime()

 

Fernando Morales:

1. I am not completely sure what do you mean because everytime I call the HMA function it would calculate everything again.

2. I can think of one way to calculate the HMA when there is a new bar by evaluating when a new bar opens using iTime()

1. Yes, every call to HMA would calculate everything again. But the difference now is that all iMA values have been cached into arrays IMA_P1 and IMA_P2. This saves two calls to the iMA function per loop iteration.

2. Right, you could detect a shift in the timeframe _tf by observing iTime(NULL,_tf,0) and on change calculate the values for the new bars. Good idea!

I think it can be done. What's now required is:

  • Declaration of IMA_P1 and IMA_P2 as double[] dynamic arrays
  • OnInit(): Calculate the number of bars you want to go into the past, for simplicity say it's 100. For (i<100), read iMA(NULL,_tf,_period/2,i) into IMA_P2 and iMA(NULL,_tf,_period,i) into IMA_P1. Remember iTime(NULL,_tf,0).
  • OnTick(): if iTime(NULL,_tf,0) differs from last return value, read new iMA values into IMA_P2 and IMA_P1 (resize and shift before).
  • HMA(): Replace loop body as suggested earlier:
    for( _index = _arraySize-1; _index >= 0; _index-- )
       vector[_index]= 2 * IMA_P2[_index] - IMA_P1[_index];
Should improve performance significantly. In addition you can think of caching HMA values to boost it further. Depends on how constant your parameters are.
 
lippmaje:

1. Yes, every call to HMA would calculate everything again. But the difference now is that all iMA values have been cached into arrays IMA_P1 and IMA_P2. This saves two calls to the iMA function per loop iteration.

2. Right, you could detect a shift in the timeframe _tf by observing iTime(NULL,_tf,0) and on change calculate the values for the new bars. Good idea!

I think it can be done. What's now required is:

  • Declaration of IMA_P1 and IMA_P2 as double[] dynamic arrays
  • OnInit(): Calculate the number of bars you want to go into the past, for simplicity say it's 100. For (i<100), read iMA(NULL,_tf,_period/2,i) into IMA_P2 and iMA(NULL,_tf,_period,i) into IMA_P1. Remember iTime(NULL,_tf,0).
  • OnTick(): if iTime(NULL,_tf,0) differs from last return value, read new iMA values into IMA_P2 and IMA_P1 (resize and shift before).
  • HMA(): Replace loop body as suggested earlier:
Should improve performance significantly. In addition you can think of caching HMA values to boost it further. Depends on how constant your parameters are.

Thanks again for your inputs.

From what I understood from your suggestion I updated the code. Not working at this moment but I want to confirm that I got it right.

int OnInit()    
{
        CacheMAs();
        return(INIT_SUCCEEDED);
}

void OnTick()   
{
        if ( !IsNewBar(PERIOD_M15) ) return;    
        CacheMAs();
}


double HMA( int _period, double& _MA1[], double& _MA2[], int _shift )
{
        int _index = 0;
        int p = (int)MathSqrt(_period);
        
        double vector[];
        double ExtMapBuffer[];
        
        if ( Bars < MAarraySize ) Alert("Not enough bars in the chart. Minimum expected is ", MAarraySize);       

        ArrayResize( vector, MAarraySize );
        ArraySetAsSeries(vector,true);
        
        ArrayResize( ExtMapBuffer, MAarraySize );
        ArraySetAsSeries(ExtMapBuffer,true);

        
        for( _index = MAarraySize-1; _index >= 0; _index-- )
                vector[_index]= 2 * _MA1[_index] - _MA2[_index];

        for( _index = 0; _index < MAarraySize - _period; _index++)
                ExtMapBuffer[_index] = iMAOnArray( vector,0, p, 0, MODE_LWMA, _index );

        return( ExtMapBuffer[_shift] );
}

double IMA1fastA[], IMA1fastB[];
double IMA2fastA[], IMA2fastB[];
double IMA1slowA[], IMA1slowB[];
double IMA2slowA[], IMA2slowB[];
int MAarraySize = HMAperiod_slow_1 * HMAperiod_slow_2;

void CacheMAs()
{
        ArrayResize( IMA1fastA, MAarraySize );
        ArrayResize( IMA1fastB, MAarraySize );
        ArrayResize( IMA2fastA, MAarraySize );
        ArrayResize( IMA2fastB, MAarraySize );
        
        ArrayResize( IMA1slowA, MAarraySize );
        ArrayResize( IMA1slowB, MAarraySize );
        ArrayResize( IMA2slowA, MAarraySize );
        ArrayResize( IMA2slowB, MAarraySize );
        
        ArraySetAsSeries( IMA1fastA, true );
        ArraySetAsSeries( IMA1fastB, true );
        ArraySetAsSeries( IMA2fastA, true );
        ArraySetAsSeries( IMA2fastB, true );
        
        ArraySetAsSeries( IMA1slowA, true );
        ArraySetAsSeries( IMA1slowB, true );
        ArraySetAsSeries( IMA2slowA, true );
        ArraySetAsSeries( IMA2slowB, true );

        for( int i = MAarraySize-1; i >= 0; i-- )
        {
                IMA1fastA[i] = iMA( NULL, HMA_TF_1, HMAperiod_fast_1/2, 0, MODE_LWMA, PRICE_TYPICAL, i );
                IMA1fastB[i] = iMA( NULL, HMA_TF_1, HMAperiod_fast_1,   0, MODE_LWMA, PRICE_TYPICAL, i );
                
                IMA2fastA[i] = iMA( NULL, HMA_TF_2, HMAperiod_fast_2/2, 0, MODE_LWMA, PRICE_TYPICAL, i );
                IMA2fastB[i] = iMA( NULL, HMA_TF_2, HMAperiod_fast_2,   0, MODE_LWMA, PRICE_TYPICAL, i );       

                IMA1slowA[i] = iMA( NULL, HMA_TF_1, HMAperiod_slow_1/2, 0, MODE_LWMA, PRICE_TYPICAL, i );
                IMA1slowB[i] = iMA( NULL, HMA_TF_1, HMAperiod_slow_1,   0, MODE_LWMA, PRICE_TYPICAL, i );       

                IMA2slowA[i] = iMA( NULL, HMA_TF_2, HMAperiod_slow_2/2, 0, MODE_LWMA, PRICE_TYPICAL, i );
                IMA2slowB[i] = iMA( NULL, HMA_TF_2, HMAperiod_slow_2,   0, MODE_LWMA, PRICE_TYPICAL, i );       
        }
}
 
Yes like this, except that I wouldn't use ArraySetAsSeries for the cache. It doesn't matter whether you access them from bottom or top because you have full control over it.