From ff01d1389d9f8641cb8a82ed6806589a5205b074 Mon Sep 17 00:00:00 2001 From: Toh4iem9 Date: Sat, 5 Sep 2026 12:00:11 +0200 Subject: [PATCH] refactor: Leak-free pointer lifecycle, bounds protection & full VWMA routing --- .../MyIncludes/Bollinger_Bands_Calculator.mqh | 469 ++++++++++++++---- 1 file changed, 375 insertions(+), 94 deletions(-) diff --git a/Include/MyIncludes/Bollinger_Bands_Calculator.mqh b/Include/MyIncludes/Bollinger_Bands_Calculator.mqh index 08e64b38..a254d62f 100644 --- a/Include/MyIncludes/Bollinger_Bands_Calculator.mqh +++ b/Include/MyIncludes/Bollinger_Bands_Calculator.mqh @@ -1,51 +1,85 @@ //+------------------------------------------------------------------+ //| Bollinger_Bands_Calculator.mqh | -//| VERSION 3.00: Refactored to use MovingAverage_Engine. | -//| Copyright 2026, xxxxxxxx | +//| Engine for John Bollinger's Classic Bollinger Bands | +//| Copyright 2026, xxxxxxxx| //+------------------------------------------------------------------+ #property copyright "Copyright 2026, xxxxxxxx" +#property version "4.00" // Leak-free pointer lifecycle, bounds protection & full VWMA routing + +#ifndef BOLLINGER_BANDS_CALCULATOR_MQH +#define BOLLINGER_BANDS_CALCULATOR_MQH #include #include //+==================================================================+ -//| CLASS 1: CBollingerBandsCalculator (Standard) | +//| CLASS 1: CBollingerBandsCalculator (Base Class) | //+==================================================================+ class CBollingerBandsCalculator { protected: - int m_period; - double m_deviation; + int m_period; + double m_deviation; + ENUM_MA_TYPE m_ma_type; + ENUM_APPLIED_PRICE_HA_ALL m_source_price; - //--- Composition: Use Moving Average Engine + //--- Composition Engine CMovingAverageCalculator *m_ma_engine; - //--- Persistent Buffers - double m_price[]; - double m_ma_buffer[]; // Internal buffer for centerline + //--- Persistent State Buffers + double m_price[]; + double m_ma_buffer[]; - //--- Updated: Accepts start_index - virtual bool PreparePriceSeries(int rates_total, int start_index, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[]); + virtual bool PreparePriceSeries(const int rates_total, const int start_index, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[]); + + virtual void CreateEngine(void); public: CBollingerBandsCalculator(void); - virtual ~CBollingerBandsCalculator(void); + virtual ~CBollingerBandsCalculator(void); - bool Init(int period, double deviation, ENUM_MA_TYPE ma_type); + //--- Enhanced Pro Init (4 Parameters) + bool Init(const int period, const double deviation, const ENUM_MA_TYPE ma_type, const ENUM_APPLIED_PRICE_HA_ALL price_source); - //--- Updated: Accepts prev_calculated - void Calculate(int rates_total, int prev_calculated, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[], - double &ma_out[], double &upper_out[], double &lower_out[]); + //--- Legacy Compatible Init (3 Parameters) + bool Init(const int period, const double deviation, const ENUM_MA_TYPE ma_type) + { + return Init(period, deviation, ma_type, PRICE_CLOSE_STD); + } - void GetPriceBuffer(double &dest_array[]); + //--- Standard Calculate (Without Volume) + void Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + double &ma_out[], double &upper_out[], double &lower_out[]); + + //--- Overloaded Calculate with Long Volume (For VWMA Support) + void Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + const long &volume[], + double &ma_out[], double &upper_out[], double &lower_out[]); + + //--- Overloaded Calculate with Double Volume (For MTF VWMA) + void Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + const double &volume[], + double &ma_out[], double &upper_out[], double &lower_out[]); + + void GetPriceBuffer(double &dest_array[]); + int GetPeriod(void) const { return m_period; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ -CBollingerBandsCalculator::CBollingerBandsCalculator(void) +CBollingerBandsCalculator::CBollingerBandsCalculator(void) : m_period(20), + m_deviation(2.0), + m_ma_type(SMA), + m_source_price(PRICE_CLOSE_STD), + m_ma_engine(NULL) { - m_ma_engine = new CMovingAverageCalculator(); + ArraySetAsSeries(m_price, false); + ArraySetAsSeries(m_ma_buffer, false); } //+------------------------------------------------------------------+ @@ -54,81 +88,54 @@ CBollingerBandsCalculator::CBollingerBandsCalculator(void) CBollingerBandsCalculator::~CBollingerBandsCalculator(void) { if(CheckPointer(m_ma_engine) != POINTER_INVALID) + { delete m_ma_engine; + m_ma_engine = NULL; + } } //+------------------------------------------------------------------+ -//| Init | +//| Factory Method (Virtual) | //+------------------------------------------------------------------+ -bool CBollingerBandsCalculator::Init(int period, double deviation, ENUM_MA_TYPE ma_type) +void CBollingerBandsCalculator::CreateEngine(void) { - m_period = (period < 1) ? 1 : period; - m_deviation = deviation; + if(CheckPointer(m_ma_engine) != POINTER_INVALID) + { + delete m_ma_engine; + m_ma_engine = NULL; + } + m_ma_engine = new CMovingAverageCalculator(); + } -// Initialize the MA engine - if(!m_ma_engine.Init(m_period, ma_type)) +//+------------------------------------------------------------------+ +//| Enhanced Pro Initialization | +//+------------------------------------------------------------------+ +bool CBollingerBandsCalculator::Init(const int period, const double deviation, const ENUM_MA_TYPE ma_type, const ENUM_APPLIED_PRICE_HA_ALL price_source) + { + m_period = (period < 1) ? 1 : period; + m_deviation = deviation; + m_ma_type = ma_type; + m_source_price = price_source; + + CreateEngine(); + if(CheckPointer(m_ma_engine) == POINTER_INVALID || !m_ma_engine.Init(m_period, m_ma_type)) return false; return true; } //+------------------------------------------------------------------+ -//| Main Calculation (Optimized) | +//| Prepare Price Series (Standard - Optimized) | //+------------------------------------------------------------------+ -void CBollingerBandsCalculator::Calculate(int rates_total, int prev_calculated, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[], - double &ma_out[], double &upper_out[], double &lower_out[]) +bool CBollingerBandsCalculator::PreparePriceSeries(const int rates_total, const int start_index, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[]) { - if(rates_total < m_period) - return; - -//--- 1. Determine Start Index - int start_index = (prev_calculated == 0) ? 0 : prev_calculated - 1; - -//--- 2. Resize Buffers if(ArraySize(m_price) != rates_total) { ArrayResize(m_price, rates_total); - ArrayResize(m_ma_buffer, rates_total); + ArraySetAsSeries(m_price, false); } -//--- 3. Prepare Price (Optimized) - if(!PreparePriceSeries(rates_total, start_index, price_type, open, high, low, close)) - return; - -//--- 4. Calculate Centerline (Using Engine on Custom Array) -// We use CalculateOnArray because we have already prepared m_price (which handles HA logic if needed) - m_ma_engine.CalculateOnArray(rates_total, prev_calculated, m_price, m_ma_buffer); - -//--- 5. Calculate Bands (Incremental) - int loop_start = MathMax(m_period - 1, start_index); - - for(int i = loop_start; i < rates_total; i++) - { - double sum_sq = 0; - // Standard Deviation Calculation - // Note: Standard Bollinger Bands use the SMA of (Price - MA)^2 if the center line is SMA. - // If the center line is EMA, usually the StdDev is still calculated over the raw period window. - for(int j = 0; j < m_period; j++) - { - double diff = m_price[i-j] - m_ma_buffer[i]; - sum_sq += diff * diff; - } - - double std_dev_val = sqrt(sum_sq / m_period); - - upper_out[i] = m_ma_buffer[i] + m_deviation * std_dev_val; - lower_out[i] = m_ma_buffer[i] - m_deviation * std_dev_val; - } - -// Copy internal MA buffer to output - ArrayCopy(ma_out, m_ma_buffer, 0, 0, rates_total); - } - -//+------------------------------------------------------------------+ -//| Prepare Price (Standard - Optimized) | -//+------------------------------------------------------------------+ -bool CBollingerBandsCalculator::PreparePriceSeries(int rates_total, int start_index, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[]) - { for(int i = start_index; i < rates_total; i++) { switch(price_type) @@ -152,7 +159,7 @@ bool CBollingerBandsCalculator::PreparePriceSeries(int rates_total, int start_in m_price[i] = (high[i] + low[i] + close[i]) / 3.0; break; case PRICE_WEIGHTED: - m_price[i] = (high[i] + low[i] + 2 * close[i]) / 4.0; + m_price[i] = (high[i] + low[i] + 2.0 * close[i]) / 4.0; break; default: m_price[i] = close[i]; @@ -162,6 +169,279 @@ bool CBollingerBandsCalculator::PreparePriceSeries(int rates_total, int start_in return true; } +//+------------------------------------------------------------------+ +//| Calculate (Standard - No Volume) | +//+------------------------------------------------------------------+ +void CBollingerBandsCalculator::Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + double &ma_out[], double &upper_out[], double &lower_out[]) + { + if(rates_total < m_period || CheckPointer(m_ma_engine) == POINTER_INVALID) + return; + +// Safe allocation of destination arrays + if(ArraySize(ma_out) != rates_total) + { + ArrayResize(ma_out, rates_total); + ArraySetAsSeries(ma_out, false); + ArrayInitialize(ma_out, EMPTY_VALUE); + } + if(ArraySize(upper_out) != rates_total) + { + ArrayResize(upper_out, rates_total); + ArraySetAsSeries(upper_out, false); + ArrayInitialize(upper_out, EMPTY_VALUE); + } + if(ArraySize(lower_out) != rates_total) + { + ArrayResize(lower_out, rates_total); + ArraySetAsSeries(lower_out, false); + ArrayInitialize(lower_out, EMPTY_VALUE); + } + +// Resize internal buffers + if(ArraySize(m_ma_buffer) != rates_total) + { + ArrayResize(m_ma_buffer, rates_total); + ArraySetAsSeries(m_ma_buffer, false); + } + + int start_index = (prev_calculated == 0) ? 0 : (prev_calculated - 1); + +// 1. Prepare Price Series + if(!PreparePriceSeries(rates_total, start_index, price_type, open, high, low, close)) + return; + +// 2. Calculate Centerline MA + m_ma_engine.CalculateOnArray(rates_total, prev_calculated, m_price, m_ma_buffer, 0); + +// 3. Clean invalid warmup range on fresh calculation + if(prev_calculated == 0) + { + for(int i = 0; i < m_period - 1; i++) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + } + } + + int loop_start = MathMax(m_period - 1, start_index); + +// 4. Calculate Rolling Standard Deviation Bands + for(int i = loop_start; i < rates_total; i++) + { + double current_mean = m_ma_buffer[i]; + if(current_mean == EMPTY_VALUE) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + continue; + } + + double sum_sq = 0.0; + for(int j = 0; j < m_period; j++) + { + double diff = m_price[i - j] - current_mean; + sum_sq += diff * diff; + } + + double std_dev_val = MathSqrt(sum_sq / (double)m_period); + + ma_out[i] = current_mean; + upper_out[i] = current_mean + m_deviation * std_dev_val; + lower_out[i] = current_mean - m_deviation * std_dev_val; + } + } + +//+------------------------------------------------------------------+ +//| Calculate (Overloaded - With Long Volume for VWMA) | +//+------------------------------------------------------------------+ +void CBollingerBandsCalculator::Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + const long &volume[], + double &ma_out[], double &upper_out[], double &lower_out[]) + { + if(rates_total < m_period || CheckPointer(m_ma_engine) == POINTER_INVALID) + return; + + if(ArraySize(ma_out) != rates_total) + { + ArrayResize(ma_out, rates_total); + ArraySetAsSeries(ma_out, false); + ArrayInitialize(ma_out, EMPTY_VALUE); + } + if(ArraySize(upper_out) != rates_total) + { + ArrayResize(upper_out, rates_total); + ArraySetAsSeries(upper_out, false); + ArrayInitialize(upper_out, EMPTY_VALUE); + } + if(ArraySize(lower_out) != rates_total) + { + ArrayResize(lower_out, rates_total); + ArraySetAsSeries(lower_out, false); + ArrayInitialize(lower_out, EMPTY_VALUE); + } + + if(ArraySize(m_ma_buffer) != rates_total) + { + ArrayResize(m_ma_buffer, rates_total); + ArraySetAsSeries(m_ma_buffer, false); + } + + int start_index = (prev_calculated == 0) ? 0 : (prev_calculated - 1); + + if(!PreparePriceSeries(rates_total, start_index, price_type, open, high, low, close)) + return; + +// Convert volume for VWMA + double vol_double[]; + ArrayResize(vol_double, rates_total); + ArraySetAsSeries(vol_double, false); + for(int k = start_index; k < rates_total; k++) + vol_double[k] = (volume[k] < 1) ? 1.0 : (double)volume[k]; + +// 2. Calculate Centerline MA with Volume + m_ma_engine.CalculateOnArray(rates_total, prev_calculated, m_price, vol_double, m_ma_buffer, 0); + + if(prev_calculated == 0) + { + for(int i = 0; i < m_period - 1; i++) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + } + } + + int loop_start = MathMax(m_period - 1, start_index); + +// 3. Calculate Rolling Standard Deviation Bands + for(int i = loop_start; i < rates_total; i++) + { + double current_mean = m_ma_buffer[i]; + if(current_mean == EMPTY_VALUE) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + continue; + } + + double sum_sq = 0.0; + for(int j = 0; j < m_period; j++) + { + double diff = m_price[i - j] - current_mean; + sum_sq += diff * diff; + } + + double std_dev_val = MathSqrt(sum_sq / (double)m_period); + + ma_out[i] = current_mean; + upper_out[i] = current_mean + m_deviation * std_dev_val; + lower_out[i] = current_mean - m_deviation * std_dev_val; + } + } + +//+------------------------------------------------------------------+ +//| Calculate (Overloaded - With Double Volume for MTF VWMA) | +//+------------------------------------------------------------------+ +void CBollingerBandsCalculator::Calculate(const int rates_total, const int prev_calculated, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[], + const double &volume[], + double &ma_out[], double &upper_out[], double &lower_out[]) + { + if(rates_total < m_period || CheckPointer(m_ma_engine) == POINTER_INVALID) + return; + + if(ArraySize(ma_out) != rates_total) + { + ArrayResize(ma_out, rates_total); + ArraySetAsSeries(ma_out, false); + ArrayInitialize(ma_out, EMPTY_VALUE); + } + if(ArraySize(upper_out) != rates_total) + { + ArrayResize(upper_out, rates_total); + ArraySetAsSeries(upper_out, false); + ArrayInitialize(upper_out, EMPTY_VALUE); + } + if(ArraySize(lower_out) != rates_total) + { + ArrayResize(lower_out, rates_total); + ArraySetAsSeries(lower_out, false); + ArrayInitialize(lower_out, EMPTY_VALUE); + } + + if(ArraySize(m_ma_buffer) != rates_total) + { + ArrayResize(m_ma_buffer, rates_total); + ArraySetAsSeries(m_ma_buffer, false); + } + + int start_index = (prev_calculated == 0) ? 0 : (prev_calculated - 1); + + if(!PreparePriceSeries(rates_total, start_index, price_type, open, high, low, close)) + return; + +// Calculate Centerline MA with Double Volume Array + m_ma_engine.CalculateOnArray(rates_total, prev_calculated, m_price, volume, m_ma_buffer, 0); + + if(prev_calculated == 0) + { + for(int i = 0; i < m_period - 1; i++) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + } + } + + int loop_start = MathMax(m_period - 1, start_index); + +// Calculate Rolling Standard Deviation Bands + for(int i = loop_start; i < rates_total; i++) + { + double current_mean = m_ma_buffer[i]; + if(current_mean == EMPTY_VALUE) + { + ma_out[i] = EMPTY_VALUE; + upper_out[i] = EMPTY_VALUE; + lower_out[i] = EMPTY_VALUE; + continue; + } + + double sum_sq = 0.0; + for(int j = 0; j < m_period; j++) + { + double diff = m_price[i - j] - current_mean; + sum_sq += diff * diff; + } + + double std_dev_val = MathSqrt(sum_sq / (double)m_period); + + ma_out[i] = current_mean; + upper_out[i] = current_mean + m_deviation * std_dev_val; + lower_out[i] = current_mean - m_deviation * std_dev_val; + } + } + +//+------------------------------------------------------------------+ +//| Get Internal Price Buffer | +//+------------------------------------------------------------------+ +void CBollingerBandsCalculator::GetPriceBuffer(double &dest_array[]) + { + int size = ArraySize(m_price); + if(size > 0) + { + ArrayResize(dest_array, size); + ArraySetAsSeries(dest_array, false); + ArrayCopy(dest_array, m_price, 0, 0, size); + } + } + //+==================================================================+ //| CLASS 2: CBollingerBandsCalculator_HA | //+==================================================================+ @@ -169,28 +449,40 @@ class CBollingerBandsCalculator_HA : public CBollingerBandsCalculator { private: CHeikinAshi_Calculator m_ha_calculator; - double m_ha_open[], m_ha_high[], m_ha_low[], m_ha_close[]; + double m_ha_open[], m_ha_high[], m_ha_low[], m_ha_close[]; protected: - virtual bool PreparePriceSeries(int rates_total, int start_index, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[]) override; + virtual bool PreparePriceSeries(const int rates_total, const int start_index, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[]) override; }; //+------------------------------------------------------------------+ //| Prepare Price (Heikin Ashi - Optimized) | //+------------------------------------------------------------------+ -bool CBollingerBandsCalculator_HA::PreparePriceSeries(int rates_total, int start_index, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[]) +bool CBollingerBandsCalculator_HA::PreparePriceSeries(const int rates_total, const int start_index, const ENUM_APPLIED_PRICE price_type, + const double &open[], const double &high[], const double &low[], const double &close[]) { if(ArraySize(m_ha_open) != rates_total) { - ArrayResize(m_ha_open, rates_total); - ArrayResize(m_ha_high, rates_total); - ArrayResize(m_ha_low, rates_total); + ArrayResize(m_ha_open, rates_total); + ArraySetAsSeries(m_ha_open, false); + ArrayResize(m_ha_high, rates_total); + ArraySetAsSeries(m_ha_high, false); + ArrayResize(m_ha_low, rates_total); + ArraySetAsSeries(m_ha_low, false); ArrayResize(m_ha_close, rates_total); + ArraySetAsSeries(m_ha_close, false); } m_ha_calculator.Calculate(rates_total, start_index, open, high, low, close, m_ha_open, m_ha_high, m_ha_low, m_ha_close); + if(ArraySize(m_price) != rates_total) + { + ArrayResize(m_price, rates_total); + ArraySetAsSeries(m_price, false); + } + for(int i = start_index; i < rates_total; i++) { switch(price_type) @@ -214,7 +506,7 @@ bool CBollingerBandsCalculator_HA::PreparePriceSeries(int rates_total, int start m_price[i] = (m_ha_high[i] + m_ha_low[i] + m_ha_close[i]) / 3.0; break; case PRICE_WEIGHTED: - m_price[i] = (m_ha_high[i] + m_ha_low[i] + 2 * m_ha_close[i]) / 4.0; + m_price[i] = (m_ha_high[i] + m_ha_low[i] + 2.0 * m_ha_close[i]) / 4.0; break; default: m_price[i] = m_ha_close[i]; @@ -224,16 +516,5 @@ bool CBollingerBandsCalculator_HA::PreparePriceSeries(int rates_total, int start return true; } -//+------------------------------------------------------------------+ -//| Get Internal Price Buffer | -//+------------------------------------------------------------------+ -void CBollingerBandsCalculator::GetPriceBuffer(double &dest_array[]) - { - int size = ArraySize(m_price); - if(size > 0) - { - ArrayResize(dest_array, size); - ArrayCopy(dest_array, m_price, 0, 0, size); - } - } +#endif // BOLLINGER_BANDS_CALCULATOR_MQH //+------------------------------------------------------------------+