//+------------------------------------------------------------------+
//|                                        PortfolioRiskAnalyzer.mq5 |
//|                    Measuring What Matters: Portfolio Risk        |
//|                    Decomposition in MQL5 — Part 1 (OpenBLAS)     |
//|                                                                  |
//|  PURPOSE: Compare naive portfolio risk against true portfolio    |
//|           risk using native MQL5 matrix/vector operations        |
//|           backed by the OpenBLAS library.                        |
//+------------------------------------------------------------------+
#property copyright   "Measuring What Matters Series"
#property link        ""
#property version     "1.00"
#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)
input double     Weight1      = 0.3333;      // Portfolio weight for Symbol 1
input double     Weight2      = 0.3333;      // Portfolio weight for Symbol 2
input double     Weight3      = 0.3334;      // Portfolio weight for Symbol 3

//+------------------------------------------------------------------+
//| Helper: Compute log returns from a close price vector            |
//+------------------------------------------------------------------+
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;
  }

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+
//| Script entry point                                               |
//+------------------------------------------------------------------+
void OnStart()
  {
   PrintDivider("=");
   Print("  PORTFOLIO RISK ANALYZER — OpenBLAS Edition");
   PrintDivider("=");

//--- 1. Prepare Weights Vector
   vector w = {Weight1, Weight2, Weight3};

//--- Validate weights sum to approximately 1.0
   double weight_sum = w.Sum();
   if(MathAbs(weight_sum - 1.0) > 0.001)
     {
      PrintFormat("WARNING: Weights sum to %.4f, not 1.0.", weight_sum);
     }

//--- 2. Gather Return Vectors
   vector returns1, returns2, returns3;
   if(!ComputeLogReturns(Symbol1, TF, LookbackBars, returns1)) return;
   if(!ComputeLogReturns(Symbol2, TF, LookbackBars, returns2)) return;
   if(!ComputeLogReturns(Symbol3, TF, LookbackBars, returns3)) return;

//--- 3. Construct Matrix (Rows = Assets, Columns = Bars)
//--- MQL5's Cov() treats each ROW as a variable (asset), so we lay out
//--- assets as rows and bars as columns — opposite of the original layout.
   matrix all_returns;
   all_returns.Init(3, LookbackBars);
   all_returns.Row(returns1, 0);
   all_returns.Row(returns2, 1);
   all_returns.Row(returns3, 2);

   Print("Step 1 & 2: Individual asset risks derived via OpenBLAS matrix...");

//--- 4. Compute Full Covariance Matrix (TRIGGERS OPENBLAS)
//--- With assets as rows, Cov() now correctly returns a [3x3] matrix
//--- where each entry [i,j] is the covariance between asset i and asset j.
   matrix cov_matrix = all_returns.Cov();

//--- Guard: must be 3x3
   if(cov_matrix.Rows() != 3 || cov_matrix.Cols() != 3)
     {
      PrintFormat("ERROR: Unexpected covariance matrix shape [%dx%d]. Expected [3x3].",
                  cov_matrix.Rows(), cov_matrix.Cols());
      return;
     }

//--- Extract individual variances from the diagonal of the covariance matrix
   vector variances = cov_matrix.Diag();
   vector volatilities = MathSqrt(variances) * 100.0;

   PrintFormat("  %-10s | Volatility: %.4f%% | Variance: %.8f", Symbol1, volatilities[0], variances[0]);
   PrintFormat("  %-10s | Volatility: %.4f%% | Variance: %.8f", Symbol2, volatilities[1], variances[1]);
   PrintFormat("  %-10s | Volatility: %.4f%% | Variance: %.8f", Symbol3, volatilities[2], variances[2]);
   PrintDivider();

//--- 5. Compute Naive Risk (Ignoring correlations)
//--- Algebraically: sum(w_i^2 * var_i). Done via OpenBLAS vector dot product.
   vector w_squared = MathPow(w, 2.0);
   double naive_variance = w_squared.Dot(variances); // TRIGGERS OPENBLAS
   double naive_vol = MathSqrt(naive_variance) * 100.0;

   Print("Step 3 & 4: Naive risk calculation...");
   PrintFormat("  Naive Portfolio Variance : %.8f", naive_variance);
   PrintFormat("  Naive Portfolio Volatility: %.4f%%", naive_vol);
   PrintDivider();

//--- 6. Compute True Portfolio Risk (TRIGGERS OPENBLAS)
//--- Matrix formulation: True Variance = w^T * Covariance_Matrix * w
//--- MatMul uses OpenBLAS level 2/3 BLAS matrix-vector multiplication gems.
   vector intermediate = cov_matrix.MatMul(w);       // Step A: (Cov * w)
   double true_variance = w.Dot(intermediate);       // Step B: w^T * (Cov * w)

   double true_vol = MathSqrt(MathAbs(true_variance)) * 100.0;
   double cross_terms = true_variance - naive_variance;
   double hidden_gap = true_vol - naive_vol;
   double gap_percent = (naive_vol > 0) ? (hidden_gap / naive_vol) * 100.0 : 0.0;

   Print("Step 5: True risk calculation via Matrix Algebra...");
   PrintFormat("  Cross-covariance contribution: %.8f", cross_terms);
   PrintFormat("  True Portfolio Variance      : %.8f", true_variance);
   PrintFormat("  True Portfolio Volatility    : %.4f%%", true_vol);
   PrintDivider("=");

//--- 7. Risk Gap Summary
   Print("  RISK GAP SUMMARY (OPENBLAS ACCELERATED)");
   PrintDivider();
   PrintFormat("  Naive Risk  (correlation ignored): %.4f%%", naive_vol);
   PrintFormat("  True Risk   (covariance included): %.4f%%", true_vol);
   PrintFormat("  Hidden Risk Gap                  : %+.4f%%", hidden_gap);
   PrintFormat("  Gap as %% of naive estimate       : %+.2f%%", gap_percent);
   PrintDivider();

   if(hidden_gap > 0.0001)
      PrintFormat("  RESULT: Portfolio is RISKIER than naive suggests by %.4f%%.", hidden_gap);
   else if(hidden_gap < -0.0001)
      PrintFormat("  RESULT: Diversification benefits found. True risk is %.4f%% LOWER.", MathAbs(hidden_gap));
   else
      Print("  RESULT: Correlations are near zero.");
   PrintDivider("=");
  }
//+------------------------------------------------------------------+
