//+------------------------------------------------------------------+ //| FourierTransform.mqh | //| MMQ — Muhammad Minhas Qamar | //| www.mql5.com/en/articles/23491 | //+------------------------------------------------------------------+ #property copyright "MMQ — Muhammad Minhas Qamar" #property link "https://www.mql5.com/en/articles/23491" #property version "1.00" #property strict #include //+------------------------------------------------------------------+ //| CDFT - the real-input Fourier front end for SFA. | //| | //| SFA does not need a full spectrum. For a window of length w it | //| keeps only the first 'l' complex Fourier coefficients, whose | //| real and imaginary parts become the l real numbers that are | //| later binned into letters. Low index = low frequency, so | //| keeping the first few coefficients is a low-pass filter: it | //| retains the coarse shape of the window and discards the | //| high-frequency jitter. That built-in denoising is the whole | //| reason SFA tolerates noisy price data. | //| | //| The transform itself is delegated to ALGLIB's FFTR1D, a real | //| FFT that accepts any window length (no power-of-two rule). We | //| simply read back the coefficients we want. The class exists to | //| give SFA one clean call and to fix the coefficient-ordering | //| convention in a single place. | //+------------------------------------------------------------------+ //--- how many real values a request for 'coeff_count' coefficients //--- yields once real and imaginary parts are laid out side by side. #define DFT_REALS_PER_COEFF 2 //+------------------------------------------------------------------+ //| Interleaving convention for the flat output array. | //| | //| ALGLIB returns coefficients as a complex[] array f[0..w/2]. | //| SFA wants a flat double[] of the low coefficients' real and | //| imaginary parts. We lay them out as | //| out = { Re(f1), Im(f1), Re(f2), Im(f2), ... } | //| starting at f[1], because f[0] (the DC term) is the window | //| mean and is identically zero after z-normalization — carrying | //| it would waste a letter on a constant. 'coeff_count' therefore | //| counts non-DC coefficients, and the flat array has | //| coeff_count * 2 entries. | //+------------------------------------------------------------------+ class CDFT { public: //--- transform raw[0..len-1] and write the first 'coeff_count' //--- non-DC coefficients, interleaved (re,im,re,im,...), into //--- out[]. out[] is resized to coeff_count*2. Returns false if //--- the window is too short to supply that many coefficients. static bool LowCoefficients(const double &raw[],int len, int coeff_count,double &out[]); //--- largest number of non-DC coefficients a length-'len' window //--- can supply. ALGLIB yields floor(len/2)+1 coefficients //--- including DC, so the usable non-DC count is floor(len/2). static int MaxCoefficients(int len) { return (len>1)?(len/2):0; } }; //+------------------------------------------------------------------+ //| Compute the low-frequency coefficients of a real window. | //| | //| 'raw' is expected to be already z-normalized by the caller so | //| that the DC term is ~0 and every window is compared on shape, | //| not on price level. We still start reading at f[1] regardless, | //| so a non-normalized window simply loses its mean here. | //+------------------------------------------------------------------+ bool CDFT::LowCoefficients(const double &raw[],int len, int coeff_count,double &out[]) { if(len<2 || coeff_count<1) return false; if(coeff_count>MaxCoefficients(len)) return false; //--- ALGLIB needs a plain double[] of exactly 'len' samples. double sample[]; ArrayResize(sample,len); for(int i=0;i