diff --git a/Include/MyIncludes/EScore_Calculator.mqh b/Include/MyIncludes/EScore_Calculator.mqh index fe3e99e0..0dde4498 100644 --- a/Include/MyIncludes/EScore_Calculator.mqh +++ b/Include/MyIncludes/EScore_Calculator.mqh @@ -1,9 +1,10 @@ //+------------------------------------------------------------------+ //| EScore_Calculator.mqh| +//| Engine for Statistical E-Score (Ehlers Smoother Z-Score) | //| Copyright 2026, xxxxxxxx| //+------------------------------------------------------------------+ #property copyright "Copyright 2026, xxxxxxxx" -#property version "1.10" // Upgraded with strict internal chronological sorting safeguards +#property version "3.20" // Fixed: Preserved Persistent Smoother History on Resize #ifndef ESCORE_CALCULATOR_MQH #define ESCORE_CALCULATOR_MQH @@ -18,101 +19,256 @@ class CEScoreCalculator private: int m_period; ENUM_SMOOTHER_TYPE m_type; - bool m_use_ha; + ENUM_APPLIED_PRICE_HA_ALL m_source_price; CEhlersSmootherCalculator *m_smoother_calc; CHeikinAshi_Calculator m_ha_calc; double m_smooth_buf[]; double m_price_buf[]; - double m_ha_open[], m_ha_high[], m_ha_low[], m_ha_close[]; - 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[]); + bool PreparePriceSeries(const int rates_total, const int start_index, + const double &open[], const double &high[], + const double &low[], const double &close[]); public: - CEScoreCalculator(); - ~CEScoreCalculator(); + CEScoreCalculator(void); + ~CEScoreCalculator(void); - bool Init(int period, ENUM_SMOOTHER_TYPE type, bool use_ha); - 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 &out_escore[]); + //--- Legacy Compatible Init (3 Parameters) + bool Init(const int period, const ENUM_SMOOTHER_TYPE type, const bool use_ha) + { + ENUM_APPLIED_PRICE_HA_ALL src = use_ha ? PRICE_HA_CLOSE : PRICE_CLOSE_STD; + return Init(period, type, src); + } + + //--- Enhanced Pro Init (3 Parameters with Full Price Enum) + bool Init(const int period, const ENUM_SMOOTHER_TYPE type, const ENUM_APPLIED_PRICE_HA_ALL price_source); + + void Calculate(const int rates_total, const int prev_calculated, + const double &open[], const double &high[], + const double &low[], const double &close[], + double &out_escore[]); + + //--- Legacy Overload with price_type parameter + 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 &out_escore[]) + { + Calculate(rates_total, prev_calculated, open, high, low, close, out_escore); + } + + int GetRequiredWarmup(void) const { return m_period + 3; } }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ -CEScoreCalculator::CEScoreCalculator() : m_period(20), m_type(SUPERSMOOTHER), m_use_ha(false), m_smoother_calc(NULL) {} +CEScoreCalculator::CEScoreCalculator(void) : m_period(20), + m_type(SUPERSMOOTHER), + m_source_price(PRICE_CLOSE_STD), + m_smoother_calc(NULL) + { + ArraySetAsSeries(m_smooth_buf, false); + ArraySetAsSeries(m_price_buf, false); + ArraySetAsSeries(m_ha_open, false); + ArraySetAsSeries(m_ha_high, false); + ArraySetAsSeries(m_ha_low, false); + ArraySetAsSeries(m_ha_close, false); + } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ -CEScoreCalculator::~CEScoreCalculator() +CEScoreCalculator::~CEScoreCalculator(void) { if(CheckPointer(m_smoother_calc) != POINTER_INVALID) + { delete m_smoother_calc; + m_smoother_calc = NULL; + } } //+------------------------------------------------------------------+ -//| Init | +//| Enhanced Pro Initialization | //+------------------------------------------------------------------+ -bool CEScoreCalculator::Init(int period, ENUM_SMOOTHER_TYPE type, bool use_ha) +bool CEScoreCalculator::Init(const int period, const ENUM_SMOOTHER_TYPE type, const ENUM_APPLIED_PRICE_HA_ALL price_source) { - m_period = (period < 2) ? 2 : period; - m_type = type; - m_use_ha = use_ha; + m_period = (period < 2) ? 2 : period; + m_type = type; + m_source_price = price_source; - if(m_use_ha) - m_smoother_calc = new CEhlersSmootherCalculator_HA(); - else - m_smoother_calc = new CEhlersSmootherCalculator(); + if(CheckPointer(m_smoother_calc) != POINTER_INVALID) + { + delete m_smoother_calc; + m_smoother_calc = NULL; + } + m_smoother_calc = new CEhlersSmootherCalculator(); if(CheckPointer(m_smoother_calc) == POINTER_INVALID) return false; - return m_smoother_calc.Init(m_period, m_type, SOURCE_PRICE); + return m_smoother_calc.Init(m_period, m_type, SOURCE_PRICE, m_source_price); } //+------------------------------------------------------------------+ -//| Calculate | +//| Prepare Price Series (Standard / Heikin Ashi) | //+------------------------------------------------------------------+ -void CEScoreCalculator::Calculate(int rates_total, int prev_calculated, ENUM_APPLIED_PRICE price_type, - const double &open[], const double &high[], const double &low[], const double &close[], +bool CEScoreCalculator::PreparePriceSeries(const int rates_total, const int start_index, + const double &open[], const double &high[], + const double &low[], const double &close[]) + { + if(ArraySize(m_price_buf) != rates_total) + { + ArrayResize(m_price_buf, rates_total); + ArraySetAsSeries(m_price_buf, false); + } + + bool is_heikin_ashi = (m_source_price <= PRICE_HA_CLOSE); + + if(is_heikin_ashi) + { + 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_close, rates_total); + + ArraySetAsSeries(m_ha_open, false); + ArraySetAsSeries(m_ha_high, false); + ArraySetAsSeries(m_ha_low, false); + ArraySetAsSeries(m_ha_close, false); + } + + m_ha_calc.Calculate(rates_total, start_index, open, high, low, close, + m_ha_open, m_ha_high, m_ha_low, m_ha_close); + + for(int i = start_index; i < rates_total; i++) + { + switch(m_source_price) + { + case PRICE_HA_OPEN: + m_price_buf[i] = m_ha_open[i]; + break; + case PRICE_HA_HIGH: + m_price_buf[i] = m_ha_high[i]; + break; + case PRICE_HA_LOW: + m_price_buf[i] = m_ha_low[i]; + break; + case PRICE_HA_MEDIAN: + m_price_buf[i] = (m_ha_high[i] + m_ha_low[i]) / 2.0; + break; + case PRICE_HA_TYPICAL: + m_price_buf[i] = (m_ha_high[i] + m_ha_low[i] + m_ha_close[i]) / 3.0; + break; + case PRICE_HA_WEIGHTED: + m_price_buf[i] = (m_ha_high[i] + m_ha_low[i] + 2.0 * m_ha_close[i]) / 4.0; + break; + case PRICE_HA_CLOSE: + default: + m_price_buf[i] = m_ha_close[i]; + break; + } + } + } + else + { + for(int i = start_index; i < rates_total; i++) + { + switch(m_source_price) + { + case PRICE_OPEN_STD: + m_price_buf[i] = open[i]; + break; + case PRICE_HIGH_STD: + m_price_buf[i] = high[i]; + break; + case PRICE_LOW_STD: + m_price_buf[i] = low[i]; + break; + case PRICE_MEDIAN_STD: + m_price_buf[i] = (high[i] + low[i]) / 2.0; + break; + case PRICE_TYPICAL_STD: + m_price_buf[i] = (high[i] + low[i] + close[i]) / 3.0; + break; + case PRICE_WEIGHTED_STD: + m_price_buf[i] = (high[i] + low[i] + 2.0 * close[i]) / 4.0; + break; + case PRICE_CLOSE_STD: + default: + m_price_buf[i] = close[i]; + break; + } + } + } + + return true; + } + +//+------------------------------------------------------------------+ +//| Main Incremental Calculation Loop | +//+------------------------------------------------------------------+ +void CEScoreCalculator::Calculate(const int rates_total, const int prev_calculated, + const double &open[], const double &high[], + const double &low[], const double &close[], double &out_escore[]) { - if(rates_total < m_period + 5) + int warmup = GetRequiredWarmup(); + if(rates_total <= warmup || CheckPointer(m_smoother_calc) == POINTER_INVALID) return; - if(CheckPointer(m_smoother_calc) == POINTER_INVALID) - return; +// Safe allocation of destination array without wiping history + if(ArraySize(out_escore) != rates_total) + { + ArrayResize(out_escore, rates_total); + ArraySetAsSeries(out_escore, false); + } -//--- Resize Internal Buffers and force strict chronological sorting +// Safe allocation of internal smoother buffer (Preserves historical elements) if(ArraySize(m_smooth_buf) != rates_total) { ArrayResize(m_smooth_buf, rates_total); - ArraySetAsSeries(m_smooth_buf, false); // Fixed: strict chronological safety on internal buffers + ArraySetAsSeries(m_smooth_buf, false); } -// 1. Prepare aligned source prices (Standard or Heikin Ashi) - if(!PreparePriceSeries(rates_total, prev_calculated, price_type, open, high, low, close)) + int start_index = (prev_calculated > 0) ? (prev_calculated - 1) : 0; + +// 1. Prepare Price Data + if(!PreparePriceSeries(rates_total, start_index, open, high, low, close)) return; -// 2. Calculate underlying Ehlers Smoother - m_smoother_calc.Calculate(rates_total, prev_calculated, price_type, open, high, low, close, m_smooth_buf); +// 2. Compute Underlying Ehlers Smoother + m_smoother_calc.Calculate(rates_total, prev_calculated, open, high, low, close, m_smooth_buf); -// 3. Compute rolling Z-Score of the difference (m_price_buf - m_smooth_buf) - int start = (prev_calculated > m_period) ? prev_calculated - 1 : m_period; +// 3. Clean invalid initial range strictly on fresh calculation + if(prev_calculated == 0) + { + for(int i = 0; i < warmup; i++) + out_escore[i] = 0.0; + } + int start = (prev_calculated > warmup) ? (prev_calculated - 1) : warmup; + if(start < warmup) + start = warmup; + +// 4. Compute Rolling Z-Score of difference (m_price_buf - m_smooth_buf) for(int i = start; i < rates_total; i++) { double current_smooth = m_smooth_buf[i]; double p = m_price_buf[i]; if(current_smooth == 0.0 || current_smooth == EMPTY_VALUE) - current_smooth = p; + { + out_escore[i] = 0.0; + continue; + } - double sum_sq_diff = 0; + double sum_sq_diff = 0.0; for(int k = 0; k < m_period; k++) { int idx = i - k; @@ -126,7 +282,7 @@ void CEScoreCalculator::Calculate(int rates_total, int prev_calculated, ENUM_APP sum_sq_diff += diff * diff; } - double std_dev = MathSqrt(sum_sq_diff / m_period); + double std_dev = MathSqrt(sum_sq_diff / (double)m_period); if(std_dev > 1.0e-9) out_escore[i] = (p - current_smooth) / std_dev; @@ -135,101 +291,5 @@ void CEScoreCalculator::Calculate(int rates_total, int prev_calculated, ENUM_APP } } -//+------------------------------------------------------------------+ -//| PreparePriceSeries | -//+------------------------------------------------------------------+ -bool CEScoreCalculator::PreparePriceSeries(int rates_total, int start_index, ENUM_APPLIED_PRICE price_type, const double &open[], const double &high[], const double &low[], const double &close[]) - { - int start = (start_index == 0) ? 0 : start_index; - - if(ArraySize(m_price_buf) != rates_total) - { - ArrayResize(m_price_buf, rates_total); - ArraySetAsSeries(m_price_buf, false); // Fixed: strict chronological safety on internal buffers - } - - if(m_use_ha) - { - 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_close, rates_total); - - ArraySetAsSeries(m_ha_open, false); - ArraySetAsSeries(m_ha_high, false); - ArraySetAsSeries(m_ha_low, false); - ArraySetAsSeries(m_ha_close, false); - } - m_ha_calc.Calculate(rates_total, start, open, high, low, close, m_ha_open, m_ha_high, m_ha_low, m_ha_close); - - for(int i = start; i < rates_total; i++) - { - switch(price_type) - { - case PRICE_CLOSE: - m_price_buf[i] = m_ha_close[i]; - break; - case PRICE_OPEN: - m_price_buf[i] = m_ha_open[i]; - break; - case PRICE_HIGH: - m_price_buf[i] = m_ha_high[i]; - break; - case PRICE_LOW: - m_price_buf[i] = m_ha_low[i]; - break; - case PRICE_MEDIAN: - m_price_buf[i] = (m_ha_high[i] + m_ha_low[i]) / 2.0; - break; - case PRICE_TYPICAL: - m_price_buf[i] = (m_ha_high[i] + m_ha_low[i] + m_ha_close[i]) / 3.0; - break; - case PRICE_WEIGHTED: - m_price_buf[i] = (m_ha_high[i] + m_ha_low[i] + 2.0 * m_ha_close[i]) / 4.0; - break; - default: - m_price_buf[i] = m_ha_close[i]; - break; - } - } - } - else - { - for(int i = start; i < rates_total; i++) - { - switch(price_type) - { - case PRICE_CLOSE: - m_price_buf[i] = close[i]; - break; - case PRICE_OPEN: - m_price_buf[i] = open[i]; - break; - case PRICE_HIGH: - m_price_buf[i] = high[i]; - break; - case PRICE_LOW: - m_price_buf[i] = low[i]; - break; - case PRICE_MEDIAN: - m_price_buf[i] = (high[i] + low[i]) / 2.0; - break; - case PRICE_TYPICAL: - m_price_buf[i] = (high[i] + low[i] + close[i]) / 3.0; - break; - case PRICE_WEIGHTED: - m_price_buf[i] = (high[i] + low[i] + 2.0 * close[i]) / 4.0; - break; - default: - m_price_buf[i] = close[i]; - break; - } - } - } - return true; - } - #endif // ESCORE_CALCULATOR_MQH //+------------------------------------------------------------------+