Article-23677-EGARCH-MQL5-V.../Regression/OLS.mqh

534 lines
23 KiB
MQL5
Raw Permalink Normal View History

2026-07-24 23:03:06 +02:00
//+------------------------------------------------------------------+
//| OLS.mqh |
//| Copyright 2023, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#include<Math\Stat\T.mqh>
#include<Math\Stat\Normal.mqh>
//+------------------------------------------------------------------+
//| Covariance type enumeration |
//+------------------------------------------------------------------+
enum ENUM_COV_TYPE
{
COV_NON_ROBUST = 0,
COV_HC0
};
//+------------------------------------------------------------------+
//| matrix multiplication |
//+------------------------------------------------------------------+
template<typename T>
matrix<T> matmul(const matrix<T>& matrix_a, const matrix<T>& matrix_b)
{
matrix<T> matrix_c = matrix<T>::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<T>::Zeros(M,N);
for(ulong m=0; m<M; ++m)
for(ulong k=0; k<K; ++k)
for(ulong n=0; n<N; ++n)
matrix_c[m][n]+=matrix_a[m][k]*matrix_b[k][n];
return(matrix_c);
}
//+------------------------------------------------------------------+
//| Matrix multiply with vector |
//+------------------------------------------------------------------+
template<typename T>
vector<T> matmul(const matrix<T>& matrix_a, const vector<T>& vector_b)
{
matrix<T> matrix_b = matrix<T>::Zeros(vector_b.Size(),1);
matrix_b.Col(vector_b,0);
matrix<T>out = 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; i<m_const_cols.Size(); i++)
{
if(!m_const_cols[i])
prediction+=(m_params[i]*inputs[k++]); // Variable coordinate projection multiplication pass
else
prediction+=(m_params[i]); // Intercept accumulation calculation pass
}
return prediction;
}
//+------------------------------------------------------------------+
//| Predict next value based on model parameters |
//+------------------------------------------------------------------+
// Shortcut helper generating point calculations on single feature lines (Simple Linear Regression)
double OLS::Predict(double _input)
{
if(m_error || (m_params.Size()-m_kconstant)>1)
{
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<m_exog.Cols(); i++)
{
temp = m_exog.Col(i);
// Columns with zero variation and a maximum value of 1.0 are identified as constants
if((temp.Max()-temp.Min()) == 0.0 && temp.Max() == 1.0)
{
count++;
m_const_cols[i]=1.0; // Mask this column position index as an intercept
}
}
return count;
}
//+------------------------------------------------------------------+
//|scale factor for the covariance matrix |
//+------------------------------------------------------------------+
// Evaluates residual variance factor ($\sigma^2$): $\frac{\sum e_i^2}{\text{DOF}_{\text{residual}}}$
void OLS::scale(void)
{
m_scale = m_resid.Dot(m_resid)/double(m_resid_dof);
}
//+------------------------------------------------------------------+
//| the variance/covariance matrix |
//+------------------------------------------------------------------+
// Scales the normalized parameter covariance matrix to capture the error variance structure
void OLS::covariance_matrix(void)
{
m_cov_params=m_norm_cov_params*m_scale;
}
//+------------------------------------------------------------------+
//|Compute the variance/covariance matrix. |
//+------------------------------------------------------------------+
matrix OLS::cov_params(void)
{
//if(!cov_p.Rows())
// {
matrix cov_p;
if(m_cov_params_default.Rows())
cov_p = m_cov_params_default;
else
{
//if(scale == EMPTY_VALUE)
// scale = m_scale;
cov_p = m_norm_cov_params * m_scale;
}
/* }
if(column.Size())
{
if(column.Size()==1)
{
matrix out = matrix::Zeros(1,1);
ulong index = ulong(column[0]);
out[0,0] = cov_p[index,index];
return out;
}
else
{
matrix out = matrix::Zeros(column.Size(),column.Size());
for(ulong i = 0; i<out.Rows(); ++i)
for(ulong j = 0; j<out.Cols(); ++j)
out[i,j] = cov_p[ulong(fabs(column[i])),ulong(fabs(column[j]))];
return out;
}
}
else
if(r_matrix.Rows())
{
if(!other.Rows())
other = r_matrix;
return r_matrix.MatMul(cov_p.MatMul(other.Transpose()));
}
else*/
return cov_p;
}
//+------------------------------------------------------------------+
//|Compute Heteroscedasticity robust covariance matrix. |
//+------------------------------------------------------------------+
matrix OLS::cov_hco(void)
{
vector het_scale = pow(m_resid,2.);
return hccm(het_scale);
}
//+------------------------------------------------------------------+
//|Covariance matrix calculation helper |
//+------------------------------------------------------------------+
matrix OLS::hccm(vector &scale)
{
matrix scale_mat = matrix::Zeros(m_pinv.Cols(),m_pinv.Rows());
for(ulong i = 0; i<m_pinv.Rows(); ++i)
scale_mat.Col(scale*m_pinv.Row(i),i);
return m_pinv.MatMul(scale_mat);
}
//+------------------------------------------------------------------+
//| standard errors of the parameter estimates |
//+------------------------------------------------------------------+
// Extracts standard errors from the square root of the main diagonal elements of the covariance matrix
void OLS::bse(void)
{
matrix cp = cov_params();
m_bse=cp.Diag();
m_bse=MathSqrt(m_bse);
}
//+------------------------------------------------------------------+
//| sum of squared errors |
//+------------------------------------------------------------------+
// Calculates total residual error metrics squared: $\sum e_i^2$
void OLS::sse(void)
{
vector sqresid=MathPow(m_resid,2);
m_sse = sqresid.Sum();
}
//+------------------------------------------------------------------+
//|likelihood function for the OLS model |
//+------------------------------------------------------------------+
// Computes log-likelihood under standard normal error assumptions:
// $\ln L = -\frac{N}{2}\ln(2\pi) - -\frac{N}{2}\ln(\frac{\text{SSE}}{N}) - \frac{N}{2}$
void OLS::llf(void)
{
double obs2=double(m_obs)/2.0;
m_llf = -obs2*log(2.0*M_PI) - obs2*log(m_sse / double(m_obs)) - obs2;
}
//+------------------------------------------------------------------+
//| Akaike's information criteria |
//+------------------------------------------------------------------+
// Computes the information quality metric with parameters scale penalty: $\text{AIC} = -2\ln L + 2K$
void OLS::aic(void)
{
double params = double(m_model_dof)+double(m_kconstant);
m_aic = -2*m_llf+2*params;
}
//+------------------------------------------------------------------+
//|Bayes' information criteria |
//+------------------------------------------------------------------+
// Computes the information quality metric with sample size weight scale penalty: $\text{BIC} = -2\ln L + K\ln N$
void OLS::bic(void)
{
double params = double(m_model_dof)+double(m_kconstant);
m_bic = -2*m_llf+MathLog(m_obs)*params;
}
//+------------------------------------------------------------------+
//|t-statistic for a given parameter estimate |
//+------------------------------------------------------------------+
// Computes $t$-scores to check the null hypothesis ($H_0: \beta_i = 0$): $t = \frac{\hat{\beta_i}}{\text{SE}(\hat{\beta_i})}$
void OLS::tvalues(void)
{
m_tvalues = m_params/m_bse;
}
//+------------------------------------------------------------------+
//|total sum of squares centered about the mean |
//+------------------------------------------------------------------+
// Computes centered variation metrics for models containing an intercept constant: $\sum (Y_i - \bar{Y})^2$
void OLS::centeredtss(void)
{
vector centered_endog = m_endog - m_endog.Mean();
m_centeredtss = centered_endog.Dot(centered_endog);
}
//+------------------------------------------------------------------+
//| The sum of the squared values of the endogenous response variable|
//+------------------------------------------------------------------+
// Computes uncentered variation metrics for regression models that pass strictly through the origin: $\sum Y_i^2$
void OLS::uncenteredtss(void)
{
m_uncenteredtss = m_endog.Dot(m_endog);
}
//+------------------------------------------------------------------+
//| Pvalue calculation |
//+------------------------------------------------------------------+
vector OLS::pvalues(bool use_t)
{
m_pvalues = vector::Zeros(m_tvalues.Size());
int error = 0;
if(use_t)
{
for(ulong i = 0; i<m_pvalues.Size(); ++i)
m_pvalues[i] = (1.0 - (double)MathCumulativeDistributionT((double)fabs(m_tvalues[i]),double(m_resid_dof),error)) * 2.0;
}
else
{
for(ulong i = 0; i<m_pvalues.Size(); ++i)
m_pvalues[i] = (1.0 - (double)MathCumulativeDistributionNormal((double)fabs(m_tvalues[i]),double(0.0),double(1.0),error)) * 2.0;
}
return m_pvalues;
}
//+------------------------------------------------------------------+
//|R-squared of the model |
//+------------------------------------------------------------------+
// Computes $R^2 = 1 - \frac{\text{SSE}}{\text{TSS}}$, selecting the appropriate TSS variant depending on intercept presence
void OLS::rsqe(void)
{
m_rsqe = (m_kconstant)? 1 - m_sse/m_centeredtss: 1 - m_sse/m_uncenteredtss;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+