//+------------------------------------------------------------------+ //| MACDp.mqh | //| Moving Average Convergence Divergence on Price | //| Astralys LLC | //| | //| The distance between the close and its own n-period simple moving | //| average: | //| | //| MACDp_t(n) = Close_t - (1/n) * sum_{i=0..n-1} Close_{t-i} | //| | //| A moving average crossover is a picture. This is the same idea | //| expressed as a number, which is what makes it optimisable: the | //| crossover is the special case where the value passes through zero.| //| | //| NORMALISATION. In raw form the value is expressed in price units, | //| so a threshold of 41 points means something very different on an | //| index at 1,400 and on the same index at 6,000. Over a long sample | //| of a growing market the indicator is not stationary, and the grid | //| ends up testing an object that changes nature halfway through. | //| Dividing by the moving average turns it into a percentage | //| deviation, which is comparable across the whole sample. | //+------------------------------------------------------------------+ #property copyright "Astralys LLC" #property link "https://pulsar-terminal.com" #property version "1.00" //+------------------------------------------------------------------+ //| Simple moving average of the last n values ending at i. | //+------------------------------------------------------------------+ double MacdpSma(const double &price[], const int i, const int n) { if(n <= 0 || i < n - 1) return(0.0); double acc = 0.0; for(int k = 0; k < n; k++) acc += price[i - k]; return(acc / n); } //+------------------------------------------------------------------+ //| MACD on price over a whole series. | //| | //| price[] input series, index 0 = oldest | //| n lookback period of the moving average | //| normalise true -> percentage deviation from the average | //| false -> raw distance in price units | //| out[] output, same size as price[]. The first n-1 values are | //| set to EMPTY_VALUE and must be skipped by the caller. | //| | //| Returns the index of the first valid value, or -1 on error. | //+------------------------------------------------------------------+ int MACDPrice(const double &price[], const int n, const bool normalise, double &out[]) { const int size = ArraySize(price); if(size <= 0 || n <= 1 || n > size) return(-1); if(ArrayResize(out, size) != size) return(-1); for(int i = 0; i < n - 1; i++) out[i] = EMPTY_VALUE; for(int i = n - 1; i < size; i++) { const double sma = MacdpSma(price, i, n); if(!normalise) { out[i] = price[i] - sma; continue; } // A flat window cannot happen on real prices, but a zero average // would blow up the ratio, so it is guarded rather than trusted. out[i] = (sma > 0.0) ? (price[i] - sma) / sma * 100.0 : 0.0; } return(n - 1); } //+------------------------------------------------------------------+