//+------------------------------------------------------------------+ //| FDI_Calculator.mqh | //| Engine for Fractal Dimension Index (Carlos Sevcik Method). | //| Measures curve complexity (1.0 = Line, 2.0 = Plane). | //| Copyright 2026, xxxxxxxx | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, xxxxxxxx" #property version "2.10" // Enterprise: Added Pure Brownian-Scaled Normalization for Finite Windows #property description "High-performance calculation engine for Fractal Dimension Index (FDI)." #ifndef FDI_CALCULATOR_MQH #define FDI_CALCULATOR_MQH #include //--- Calculation Method Selector #ifndef ENUM_FDI_METHOD_DEFINED #define ENUM_FDI_METHOD_DEFINED enum ENUM_FDI_METHOD { FDI_NORMALIZED_BROWNIAN, // Pure Brownian Scale (D = 1.50 at Random Walk for ANY N - Recommended) FDI_CANONICAL_SEVCIK, // Unbiased Sevcik 1998 (D ~ 1.25-1.28 for N=30) FDI_RETAIL_CLASSIC // Legacy Retail Approximation (with +ln(2) numerator shift) }; #endif //+==================================================================+ //| CLASS: CFDICalculator | //+==================================================================+ class CFDICalculator { protected: int m_period; ENUM_FDI_METHOD m_method; double m_price[]; // Persistent source price buffer // Precalculated invariant constants for ALU pipelining double m_diff_x; // 1.0 / (N - 1) double m_diff_x_sq; // (1.0 / (N - 1))^2 double m_log_denominator; // ln(N - 1) or ln(2 * (N - 1)) double m_inv_log_denom; // Reciprocal of denominator 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[]); public: CFDICalculator(void); virtual ~CFDICalculator(void) {}; bool Init(const int period, const ENUM_FDI_METHOD method = FDI_NORMALIZED_BROWNIAN); int GetPeriod(void) const { return m_period; } double GetTheoreticalRandomWalkD(void) const; 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_fdi[], const int history_limit = 0); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CFDICalculator::CFDICalculator(void) : m_period(30), m_method(FDI_NORMALIZED_BROWNIAN), m_diff_x(0.0), m_diff_x_sq(0.0), m_log_denominator(1.0), m_inv_log_denom(1.0) { ArraySetAsSeries(m_price, false); } //+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ bool CFDICalculator::Init(const int period, const ENUM_FDI_METHOD method) { m_period = (period < 10) ? 10 : period; m_method = method; m_diff_x = 1.0 / (double)(m_period - 1); m_diff_x_sq = m_diff_x * m_diff_x; if(m_method == FDI_NORMALIZED_BROWNIAN) m_log_denominator = MathLog((double)(m_period - 1)); else m_log_denominator = MathLog(2.0 * (double)(m_period - 1)); m_inv_log_denom = (m_log_denominator > 1.0e-9) ? (1.0 / m_log_denominator) : 1.0; return true; } //+------------------------------------------------------------------+ //| Theoretical Random Walk Dimension for Current Configuration | //+------------------------------------------------------------------+ double CFDICalculator::GetTheoreticalRandomWalkD(void) const { if(m_method == FDI_NORMALIZED_BROWNIAN) return 1.50; // Strictly normalized by design if(m_method == FDI_CANONICAL_SEVCIK) return 1.0 + (0.5 * MathLog((double)(m_period - 1)) / m_log_denominator); // Legacy Retail return 1.0 + ((0.5 * MathLog((double)(m_period - 1)) + MathLog(2.0)) / m_log_denominator); } //+------------------------------------------------------------------+ //| Prepare Price Series | //+------------------------------------------------------------------+ bool CFDICalculator::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_price) != rates_total) { ArrayResize(m_price, rates_total); ArraySetAsSeries(m_price, false); } for(int i = start_index; i < rates_total; i++) { switch(price_type) { case PRICE_OPEN: m_price[i] = open[i]; break; case PRICE_HIGH: m_price[i] = high[i]; break; case PRICE_LOW: m_price[i] = low[i]; break; case PRICE_MEDIAN: m_price[i] = (high[i] + low[i]) * 0.5; break; case PRICE_TYPICAL: m_price[i] = (high[i] + low[i] + close[i]) * 0.333333333333; break; case PRICE_WEIGHTED: m_price[i] = (high[i] + low[i] + 2.0 * close[i]) * 0.25; break; case PRICE_CLOSE: default: m_price[i] = close[i]; break; } } return true; } //+------------------------------------------------------------------+ //| Main Calculation | //+------------------------------------------------------------------+ void CFDICalculator::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_fdi[], const int history_limit) { if(rates_total < m_period) return; if(ArraySize(out_fdi) != rates_total) { ArrayResize(out_fdi, rates_total); ArraySetAsSeries(out_fdi, false); } int start_prep = (prev_calculated > 0) ? (prev_calculated - 1) : 0; if(!PreparePriceSeries(rates_total, start_prep, price_type, open, high, low, close)) return; int start_index = (prev_calculated > m_period) ? (prev_calculated - 1) : m_period; if(history_limit > 0 && prev_calculated == 0) { int limit_start = rates_total - history_limit; if(limit_start > start_index) start_index = limit_start; } if(prev_calculated == 0) { for(int i = 0; i < start_index; i++) out_fdi[i] = EMPTY_VALUE; } double ln_2 = MathLog(2.0); for(int i = start_index; i < rates_total; i++) { // 1. Extreme Price Range in window double highest = -DBL_MAX; double lowest = DBL_MAX; int win_start = i - m_period + 1; for(int k = 0; k < m_period; k++) { double p = m_price[win_start + k]; if(p > highest) highest = p; if(p < lowest) lowest = p; } double price_range = highest - lowest; // 2. Waveform Path Length (L) in Unit Square double path_length = 0.0; if(price_range > 1.0e-9) { double inv_range = 1.0 / price_range; for(int k = 1; k < m_period; k++) { double diff_price = m_price[win_start + k] - m_price[win_start + k - 1]; double diff_y = diff_price * inv_range; path_length += MathSqrt(m_diff_x_sq + (diff_y * diff_y)); } } else { path_length = 1.0; } // 3. Normalized Fractal Dimension Calculation if(path_length > 1.0e-9) { double ln_L = MathLog(path_length); if(m_method == FDI_NORMALIZED_BROWNIAN) { // Anchored: Flat Line = 1.0, Brownian Random Walk = 1.50 out_fdi[i] = 1.0 + (ln_L * m_inv_log_denom); } else if(m_method == FDI_CANONICAL_SEVCIK) { out_fdi[i] = 1.0 + (ln_L * m_inv_log_denom); } else { out_fdi[i] = 1.0 + ((ln_L + ln_2) * m_inv_log_denom); } } else { out_fdi[i] = 1.0; } } } #endif // FDI_CALCULATOR_MQH //+------------------------------------------------------------------+