//+------------------------------------------------------------------+ //| CovarianceMatrixPrinter.mq5 | //| Measuring What Matters: Portfolio Risk | //| Decomposition in MQL5 — Part 2 | //| | //| PURPOSE: Build a full 3x3 covariance matrix from multi-symbol | //| return data using native OpenBLAS operations, wrap it | //| in an OOP class, verify matrix symmetry, perform | //| eigenvalue decomposition using the native MQL5 .Eig(), | //| and print the risk factor loadings to the journal. | //+------------------------------------------------------------------+ #property copyright "Measuring What Matters Series — Part 2" #property link "" #property version "1.01" #property script_show_inputs //--- Input parameters input string Symbol1 = "EURUSD"; // First symbol input string Symbol2 = "GBPUSD"; // Second symbol input string Symbol3 = "XAUUSD"; // Third symbol input ENUM_TIMEFRAMES TF = PERIOD_H1; // Timeframe input int LookbackBars = 100; // Lookback period (bars) //+------------------------------------------------------------------+ //| Helper: Print a formatted divider line | //+------------------------------------------------------------------+ void PrintDivider(string ch = "-", int len = 55) { string line = ""; for(int i = 0; i < len; i++) line += ch; Print(line); } //+------------------------------------------------------------------+ //| Helper: Compute log returns | //+------------------------------------------------------------------+ bool ComputeLogReturns(const string symbol, ENUM_TIMEFRAMES tf, int bars, vector &log_returns) { vector prices; if(!prices.CopyRates(symbol, tf, COPY_RATES_CLOSE, 0, bars + 1)) { PrintFormat("ERROR: Could not copy rates for %s. Error: %d", symbol, GetLastError()); return false; } log_returns.Init(bars); for(int i = 0; i < bars; i++) { if(prices[i] <= 0.0 || prices[i + 1] <= 0.0) { log_returns[i] = 0.0; continue; } log_returns[i] = MathLog(prices[i] / prices[i + 1]); } return true; } //+------------------------------------------------------------------+ //| CLASS: CCovarianceMatrix | //| Encapsulates the full covariance matrix workflow: | //| - Stores symbol names and scales matrix dimensions dynamically | //| - Builds the covariance matrix natively using OpenBLAS | //| - Verifies structural matrix symmetry | //| - Provides eigenvalue decomposition via native .Eig() API | //| - Prints formatted matrix grids and risk factor loading spectra| //+------------------------------------------------------------------+ class CCovarianceMatrix { private: int m_size; string m_symbols[]; matrix m_cov; public: //--- Constructor CCovarianceMatrix(const string &symbols[], int size) { m_size = size; ArrayResize(m_symbols, m_size); for(int i = 0; i < m_size; i++) m_symbols[i] = symbols[i]; m_cov.Init(m_size, m_size); } //--- Build the matrix from return vectors via OpenBLAS structures bool Build(const vector &returns[], int n_vectors) { if(n_vectors != m_size || n_vectors < 1) { Print("ERROR: Number of return vectors must match matrix size."); return false; } //--- Collect number of columns/observations from the first asset vector ulong columns = returns[0].Size(); matrix all_returns; all_returns.Init(m_size, columns); //--- Systematically populate rows (Assets as Rows, Observations as Columns) for(int i = 0; i < m_size; i++) all_returns.Row(returns[i], i); //--- Compute full N x N matrix via OpenBLAS engine m_cov = all_returns.Cov(); return true; } //--- Verify matrix symmetry (computational requirement) bool IsSymmetric(double tol = 1e-10) const { for(int i = 0; i < m_size; i++) for(int j = i + 1; j < m_size; j++) if(MathAbs(m_cov[i][j] - m_cov[j][i]) > tol) return false; return true; } //--- Basic Accessor Methods double Get(int row, int col) { return m_cov[row][col]; } matrix GetMatrix() { return m_cov; } //--- Print a clean, formatted N x N matrix grid to the journal void PrintMatrix() { PrintDivider("="); Print(" COVARIANCE MATRIX (OPENBLAS ACCELERATED)"); PrintDivider(); string header = StringFormat(" %12s", ""); for(int col = 0; col < m_size; col++) header += StringFormat(" %-14s", m_symbols[col]); Print(header); PrintDivider(); for(int row = 0; row < m_size; row++) { string line = StringFormat(" %-12s", m_symbols[row]); for(int col = 0; col < m_size; col++) line += StringFormat(" %+.8f ", m_cov[row][col]); Print(line); } PrintDivider("="); } //--- Performs Eigendecomposition and generates organized Risk Factor spectrums void PrintEigenSpectrum() { matrix eigenvectors; vector eigenvalues; //--- Executing native MQL5 OpenBLAS decomposition (Eigenvectors first, then Eigenvalues) if(!m_cov.Eig(eigenvectors, eigenvalues)) { Print("ERROR: Eigenvalue decomposition failed."); return; } int n = (int)eigenvalues.Size(); //--- Initialize index array for sorting risk shares from largest to smallest int idx[]; ArrayResize(idx, n); for(int i = 0; i < n; i++) idx[i] = i; //--- Simple sorting loop (Bubble sort variant for sorting index mappings) for(int i = 0; i < n - 1; i++) for(int j = i + 1; j < n; j++) if(eigenvalues[idx[j]] > eigenvalues[idx[i]]) { int tmp = idx[i]; idx[i] = idx[j]; idx[j] = tmp; } double total_variance = 0.0; for(int i = 0; i < n; i++) total_variance += eigenvalues[i]; PrintDivider("="); Print(" EIGENVALUE SPECTRUM — RISK FACTOR DECOMPOSITION"); PrintDivider(); PrintFormat(" Total Portfolio Variance: %.8f", total_variance); PrintDivider(); //--- Print sorted factors alongside their respective asset loadings for(int f = 0; f < n; f++) { int k = idx[f]; double ev = eigenvalues[k]; double pct = (total_variance > 0) ? (ev / total_variance) * 100.0 : 0.0; PrintFormat(" Factor %d | Eigenvalue: %+.8f | Risk Share: %.2f%%", f + 1, ev, pct); for(int s = 0; s < m_size; s++) PrintFormat(" Loading on %-10s : %+.6f", m_symbols[s], eigenvectors[s][k]); PrintDivider(); } //--- Check dominant variance concentration metrics double factor1_pct = (total_variance > 0) ? (eigenvalues[idx[0]] / total_variance) * 100.0 : 0.0; if(factor1_pct > 70.0) PrintFormat(" WARNING: Factor 1 holds %.2f%% of total variance. " "Portfolio is CONCENTRATED.", factor1_pct); else PrintFormat(" RESULT: Risk distributed. Factor 1 = %.2f%% of variance.", factor1_pct); PrintDivider("="); } }; //+------------------------------------------------------------------+ //| Script entry point | //+------------------------------------------------------------------+ void OnStart() { PrintDivider("="); Print(" COVARIANCE MATRIX PRINTER — Part 2 Script"); Print(" Measuring What Matters: Portfolio Risk Decomposition"); PrintDivider("="); PrintFormat(" Symbols : %s | %s | %s", Symbol1, Symbol2, Symbol3); PrintFormat(" Timeframe: %s", EnumToString(TF)); PrintFormat(" Lookback : %d bars", LookbackBars); PrintDivider(); vector returns[]; ArrayResize(returns, 3); string symbols[3]; symbols[0] = Symbol1; symbols[1] = Symbol2; symbols[2] = Symbol3; //--- 1. Compute return data Print("Step 1: Computing log return series..."); for(int i = 0; i < 3; i++) { if(!ComputeLogReturns(symbols[i], TF, LookbackBars, returns[i])) return; } PrintFormat(" Log returns computed: %d bars per symbol.", LookbackBars); PrintDivider(); //--- 2. Build and verify matrix Print("Step 2: Building covariance matrix..."); CCovarianceMatrix cov_matrix(symbols, 3); if(!cov_matrix.Build(returns, 3)) { Print("ERROR: Could not build covariance matrix. Exiting."); return; } Print(" Matrix built successfully."); //--- Validate structural symmetry properties from outline requirements if(cov_matrix.IsSymmetric()) Print(" Computational Verification: Matrix symmetry test PASSED."); else Print(" WARNING: Structural asymmetry detected."); cov_matrix.PrintMatrix(); //--- 3. Execute decomposition Print("Step 3: Performing eigenvalue decomposition..."); cov_matrix.PrintEigenSpectrum(); PrintDivider("="); Print(" Script complete. See article Part 2 for full breakdown."); PrintDivider("="); } //+------------------------------------------------------------------+