//+------------------------------------------------------------------+ //| OLS.mqh | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #include #include //+------------------------------------------------------------------+ //| Covariance type enumeration | //+------------------------------------------------------------------+ enum ENUM_COV_TYPE { COV_NON_ROBUST = 0, COV_HC0 }; //+------------------------------------------------------------------+ //| matrix multiplication | //+------------------------------------------------------------------+ template matrix matmul(const matrix& matrix_a, const matrix& matrix_b) { matrix matrix_c = matrix::Zeros(0,0); if(matrix_a.Cols()!=matrix_b.Rows()) return(matrix_c); ulong M=matrix_a.Rows(); ulong K=matrix_a.Cols(); ulong N=matrix_b.Cols(); matrix_c=matrix::Zeros(M,N); for(ulong m=0; m vector matmul(const matrix& matrix_a, const vector& vector_b) { matrix matrix_b = matrix::Zeros(vector_b.Size(),1); matrix_b.Col(vector_b,0); matrixout = matmul(matrix_a,matrix_b); return out.Col(0); } //+------------------------------------------------------------------+ //| Ordinary least squares class | //+------------------------------------------------------------------+ // Class implementing classical Ordinary Least Squares (OLS) linear regression estimation. // Uses Singular Value Decomposition (SVD) and Moore-Penrose pseudo-inverses for numerical stability. class OLS { private: bool m_const_prepended; // Flag indicating if a baseline constant column is tracked in the matrix matrix m_exog, // Design Matrix ($X$ matrix of independent exogenous predictors) m_pinv, // Calculated Moore-Penrose pseudo-inverse of the design matrix ($X^+$) m_cov_params, // Scaled covariance matrix of the calculated parameters variance limits m_m_error, // Fallback sentinel error matrix populated during failure states m_norm_cov_params, // Normalized parameters covariance matrix base: $(X^T X)^{-1}$ m_cov_params_default; // Default covariance matrix vector m_endog, // Dependent response variables vector ($Y$ vector of endogenous data) m_weights, // Weights profile configured for data vectors (initialized to 1s) m_singularvalues, // Singular values diagonal array populated by the SVD decomposition pass m_params, // Computed regression coefficients/slopes result vector ($\beta$ solution) m_tvalues, // T-statistic values validating individual parameter statistical significance m_bse, // Standard errors associated with calculated parameter estimates m_const_cols, // Vector masking which specific columns serve as constant intercept vectors m_resid, // Prediction residual error vector: $e = Y - \hat{Y}$ m_pvalues; // Parameter pvalues ulong m_obs, // Sample count: Number of observations rows in the matrix m_model_dof, // Degrees of freedom of the model features (rank minus intercept tracking) m_resid_dof, // Degrees of freedom of the calculated residuals ($N - \text{rank}$) m_kconstant, // Number of constant intercept columns detected in the matrix model m_rank; // Computed mathematical rank of the design matrix ENUM_COV_TYPE m_cov_type; // Covariance calculation method double m_aic, // Akaike Information Criterion assessing comparative specification quality m_bic, // Bayesian Information Criterion adding stricter parameters penalty layers m_scale, // Variance scale parameter estimate of the model error component ($\sigma^2$) m_llf, // Log-Likelihood function estimation value under normality assumptions m_sse, // Sum of Squared Errors / Residual Sum of Squares (RSS) m_rsqe, // Coefficient of Determination ($R^2$) indicating variance explanation rates m_centeredtss, // Centered Total Sum of Squares tracking variance deviations about the mean m_uncenteredtss; // Uncentered Total Sum of Squares used when no intercept constant exists uint m_error; // Boolean/integer indicator flag marking invalid calculation states // Private computation pipeline steps ulong countconstants(void); // Scans columns to locate and mask intercept structures void scale(void); // Computes residual variance scale factor void sse(void); // Accumulates squared error metric values void rsqe(void); // Computes the fit determination score ($R^2$) void centeredtss(void); // Computes variation tracking around response mean values void uncenteredtss(void); // Calculates total raw coordinate variations void aic(void); // Evaluates Akaike information scores void bic(void); // Evaluates Bayesian information scores void bse(void); // Extracts coefficient standard deviations profiles void llf(void); // Evaluates Gaussian log likelihood values void tvalues(void); // Scales parameter estimations by standard deviations void covariance_matrix(void);// Scales normal distributions by the variance metric matrix cov_params(void); // Calculate covariance matrix matrix hccm(vector &scale); // Covariance helper matrix cov_hco(void); // Heteroscedasticity robust covariance matrix. vector pvalues(bool use_t); // Pvalue calculation public: // Constructor & Destructor OLS(void); ~OLS(void); // Public Execution Controllers bool Fit(vector &y_vars,matrix &x_vars,ENUM_COV_TYPE cov_type = COV_NON_ROBUST,bool use_t = false); // Core fitting routine executing regression logic double Predict(vector &inputs); // Generates predictions for multivariate input arrays double Predict(double _input); // Generates predictions for univariate input models // Public Property Accessors / Getters ulong ModelDOF(void) { if(m_error) return 0; else return m_model_dof;} ulong ResidDOF(void) { if(m_error) return 0; else return m_resid_dof;} double Scale(void) { if(m_error) return EMPTY_VALUE; else return m_scale; } double Aic(void) { if(m_error) return EMPTY_VALUE; else return m_aic; } double Bic(void) { if(m_error) return EMPTY_VALUE; else return m_bic; } double Sse(void) { if(m_error) return EMPTY_VALUE; else return m_sse; } double Rsqe(void) { if(m_error) return EMPTY_VALUE; else return m_rsqe; } double C_tss(void) { if(m_error) return EMPTY_VALUE; else return m_centeredtss;} double Loglikelihood(void) { if(m_error) return EMPTY_VALUE; return m_llf; } vector Tvalues(void) { if(m_error) return m_m_error.Col(0); return m_tvalues; } vector Residuals(void) { if(m_error) return m_m_error.Col(0); return m_resid; } vector ModelParameters(void) { if(m_error) return m_m_error.Col(0); return m_params; } vector Bse(void) { if(m_error) return m_m_error.Col(0); return m_bse; } matrix CovarianceMatrix(void) { if(m_error) return m_m_error; return m_cov_params; } vector Pvalues(void) { if(m_error) return m_m_error.Col(0); return m_pvalues; } }; //+------------------------------------------------------------------+ //| constructor | //+------------------------------------------------------------------+ OLS::OLS(void) { m_kconstant = 0; m_obs = 0; m_model_dof = 0; m_resid_dof = 0; m_rank = 0; m_aic = 0; m_bic = 0; m_scale = 0; m_llf = 0; m_error = 1; // Default to error state until data is successfully fitted // Instantiate an fallback matrix populated with error placeholders m_m_error.Resize(2,2); m_m_error.Fill(EMPTY_VALUE); } //+------------------------------------------------------------------+ //|Destructor | //+------------------------------------------------------------------+ OLS::~OLS(void) { } //+------------------------------------------------------------------+ //| Fit data | //+------------------------------------------------------------------+ // Executes parameter estimation using linear algebraic decomposition techniques bool OLS::Fit(vector &y_vars,matrix &x_vars,ENUM_COV_TYPE cov_type = COV_NON_ROBUST,bool use_t = false) { // Synchronize raw input objects to internal structural data slots m_endog = y_vars; m_exog = x_vars; m_cov_type = cov_type; // Identify if an intercept constant is bundled into the independent matrix configurations m_kconstant = countconstants(); m_error = 0; // Reset structural state counters m_model_dof = m_resid_dof = m_rank = m_obs= 0; m_aic = m_bic= m_llf=0; m_obs=m_exog.Rows(); // Number of observation rows available for modeling m_weights = vector::Ones(m_obs); // Standard default OLS tracks even weighting profiles m_rank = m_exog.Rank(); // Extract initial matrix rank profile configuration counts m_model_dof = m_rank - m_kconstant; m_resid_dof = m_obs - m_rank; // Enforce basic matrix dimension validation checks if(y_vars.Size()!=x_vars.Rows()) { Print(__FUNCTION__," ",__LINE__," Error Invalid inputs Rows in x_vars not equal to length of y_vars"); m_error = 1; return false; } matrix U,V; ::ResetLastError(); // Execute SVD factor processing to assert geometric rank configurations stably if(!m_exog.SVD(U,V,m_singularvalues)) { Print(__FUNCTION__," ",__LINE__," SVD operation failed : ",::GetLastError()); m_error = 1; return false; } // Extract the Moore-Penrose pseudo-inverse matrix: $X^+ = (X^T X)^{-1} X^T$ m_pinv = m_exog.PInv(); matrix pinv_t = m_pinv.Transpose(); // Compute normalized parameter covariances layout: $X^+ (X^+)^T = (X^T X)^{-1}$ m_norm_cov_params = matmul(m_pinv,pinv_t); matrix diag; if(!diag.Diag(m_singularvalues)) { Print(__FUNCTION__," ",__LINE__," Diag operation failed : ",::GetLastError()); m_error = 1; return false; } m_rank = diag.Rank(); // Update matrix structural rank estimations via non-zero diagonal entries // Compute parameter coefficients vector: $\hat{\beta} = X^+ Y$ m_params = matmul(m_pinv,m_endog); // Recompute final safe degrees of freedom allocations post-rank evaluation m_model_dof = m_rank - m_kconstant; m_resid_dof = m_obs - m_rank; // Isolate model tracking residuals: $e = Y - X\hat{\beta}$ m_resid = m_endog - matmul(m_exog,m_params); // Calculate defaulte covariance switch(m_cov_type) { case COV_NON_ROBUST: m_cov_params_default = matrix::Zeros(0,0); break; case COV_HC0: m_cov_params_default = cov_hco(); break; } // Execute downstream statistical attribute calculation routines scale(); covariance_matrix(); bse(); sse(); llf(); centeredtss(); uncenteredtss(); rsqe(); aic(); bic(); tvalues(); pvalues(use_t); return true; } //+------------------------------------------------------------------+ //| Predict next value based on model parameters | //+------------------------------------------------------------------+ // Evaluates dot product estimations while maintaining spatial transformations for masked constants double OLS::Predict(vector &inputs) { if(m_error) { Print("Invalid model"); return EMPTY_VALUE; } // Ensure supplied non-constant features align with parameter dimensions if(inputs.Size()!=(m_params.Size()-m_kconstant)) { Print("invalid inputs: supplied vector does not match size of model parameters"); return EMPTY_VALUE; } double prediction = 0; // Conditional dot-product tracking that maps slope parameters onto raw inputs or accumulates constant values for(ulong i = 0, k = 0; i1) { if(m_error) Print("Invalid model"); else Print("invalid inputs: insufficient number of predictors supplied"); return EMPTY_VALUE; } double prediction = 0; if(m_kconstant) // Apply single feature multiplication adjusting indexes based on intercepted location patterns prediction=(m_const_cols[0])?m_params[0]+(m_params[1]*_input):m_params[1]+(m_params[0]*_input); else prediction=m_params[0]*_input; return prediction; } //+------------------------------------------------------------------+ //|Count the number of constants in RHS matrix | //+------------------------------------------------------------------+ // Scans columns where the difference between max and min values is zero, indicating an invariant intercept vector ulong OLS::countconstants(void) { ulong count = 0; vector temp; m_const_cols = vector::Zeros(m_exog.Cols()); for(ulong i =0; i