Dynamic Value at Risk Calculation

 

Came across this earlier and think it's pretty useful for Multi Symbol Portfolio Analysis.

I've converted the code provided in this video: https://www.youtube.com/watch?v=3FV8PTikKc0

So that it dynamically measures the Value at Risk of the open trades on the Account.

Note: the issue that I'm having is that Buy & Sell trades for the same symbol both add to the value at risk.

This should not happen, go to 8:30 in the video to understand why

It would be good to discuss potential solutions for this.

The key part of the code that needs to be changed is:

   string CurrPortAssets[];
   double CurrPortLotSizes[];      //+ve - LONG,  -ve - SHORT
   
   uint positions = PositionsTotal();
   ArrayResize(CurrPortAssets,positions);
   ArrayResize(CurrPortLotSizes,positions);
   for (uint i=0; i<positions; i++)
      {
      string activo = PositionGetSymbol(i);
      PositionSelect(activo);
      double cantidad = PositionGetDouble(POSITION_VOLUME);
      long direction = PositionGetInteger(POSITION_TYPE);
      CurrPortAssets[i] = activo;
      CurrPortLotSizes[i] = direction==POSITION_TYPE_BUY? cantidad : -cantidad;
      }
      
Coding Incremental VaR in MQL4 and MQL5 for Better Trading Risk Management | Free Code Download
Coding Incremental VaR in MQL4 and MQL5 for Better Trading Risk Management | Free Code Download
  • 2022.03.29
  • www.youtube.com
Incremental Value at Risk (VaR) can assist with better trading and portfolio risk management. This MQL4 and MQL5 coding tutorial with free code download can ...
 
here is the entire .mqh file you need, which includes netted positions. for example
// INPUT:
InputAssets = ["EURUSD", "GBPUSD", "EURUSD"] 
InputSizes = [+1.0, +0.5, -0.3]

// OUTPUT:
NetAssets = ["EURUSD", "GBPUSD"]

NetSizes = [+0.7, +0.5]  // 1.0 + (-0.3) = 0.7


use these variables as you like(adjust) in your EA.

CPortfolioRiskMan *portfolioRisk; 

ENUM_TIMEFRAMES VaRTimeframe = PERIOD_D1; 

double MaxPortfolioVaR = 1500.0;              

double CorrelationThreshold = 0.4;    

int VaRPeriods = 14;                     

int CorrelationPeriods = 30; 

 //+------------------------------------------------------------------+
//|                                    Portfolio_Risk_Management.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright     "Copyright 2022, Darwinex"
#property link          "https://www.darwinex.com"
#property description   "Portfolio Risk Management Module - FIXED WITH POSITION NETTING"
#property strict

#include <Math\Stat\Math.mqh>

class CPortfolioRiskMan
{
   public:   
      double MultiPositionVaR; 
      
      void   CPortfolioRiskMan(ENUM_TIMEFRAMES VaRTF, int SDPeriods, int CorrPeriods); //CONSTRUCTOR
      bool   CalculateVaR(string &Assets[], double &AssetPosSizes[]);

      
   private:
      ENUM_TIMEFRAMES ValueAtRiskTimeframe;
      int   StandardDeviationPeriods;
      int   CorrelationPeriods;
      
      bool  GetAssetStdDevReturns(string VolSymbolName, double &StandardDevOfReturns);
      bool  GetAssetCorrelation(string CorrSymbolName1, string CorrSymbolName2, double &PearsonR);
      bool  NetPositionsBySymbol(string &InputAssets[], double &InputSizes[], string &NetAssets[], double &NetSizes[]); // NEW FUNCTION
};

//CONSTRUCTOR
void CPortfolioRiskMan::CPortfolioRiskMan(ENUM_TIMEFRAMES VaRTF, int SDPeriods, int CorrPeriods)  
{
   MessageBox("Warning: This risk management module now includes POSITION NETTING to properly handle multiple positions in the same symbol. This ensures accurate VaR calculations for hedged positions.\n\nStandard disclaimer: Only suitable for assets managed by your broker in your account currency.", "VaR MODULE - WITH POSITION NETTING", MB_ICONINFORMATION);  
   
   ValueAtRiskTimeframe     = VaRTF;
   StandardDeviationPeriods = SDPeriods;
   CorrelationPeriods       = CorrPeriods;
}

bool CPortfolioRiskMan::CalculateVaR(string &Assets[], double &AssetPosSizes[])                 
{  
   Print("Running " + __FUNCTION__ + "() - WITH POSITION NETTING");
   
   //######################################## 
   // STEP 1: NET POSITIONS BY SYMBOL (NEW!)
   //########################################
   
   string NettedAssets[];
   double NettedSizes[];
   
   if(!NetPositionsBySymbol(Assets, AssetPosSizes, NettedAssets, NettedSizes))
   {
      Alert("Error during position netting in: " + __FUNCTION__ + "()");
      return false;
   }
   
   // Check if we have any positions left after netting
   if(ArraySize(NettedAssets) == 0)
   {
      Print("ℹ️ All positions are fully hedged - Portfolio VaR = 0");
      MultiPositionVaR = 0.0;
      return true;
   }
   
   Print("📊 Portfolio after netting: ", ArraySize(NettedAssets), " unique symbols");
   
   //#########################################
   //STEP 2: CALCULATE STD DEV OF RETURNS FOR NETTED POSITIONS
   //#########################################
   
   double stdDevReturns[];                       
   ArrayResize(stdDevReturns, ArraySize(NettedAssets));
   
   for(int assetLoop = 0; assetLoop < ArraySize(NettedAssets); assetLoop++)
   {
      if(!GetAssetStdDevReturns(NettedAssets[assetLoop], stdDevReturns[assetLoop])) 
      {
         Alert("Error calculating Std Dev of Returns for " + NettedAssets[assetLoop] + " in: " + __FUNCTION__ + "()");
         return false;
      }
   }
   
   //########################################
   //STEP 3: CALCULATE CORRELATION COEFFICIENTS FOR NETTED POSITIONS
   //########################################

   double correlCoeff[][30];
   ArrayResize(correlCoeff, ArraySize(NettedAssets));
   
   for(int assetALoop = 0; assetALoop < ArraySize(NettedAssets); assetALoop++)
   {
      for(int assetBLoop = 0; assetBLoop < ArraySize(NettedAssets); assetBLoop++)
      {
         if(!GetAssetCorrelation(NettedAssets[assetALoop], NettedAssets[assetBLoop], correlCoeff[assetALoop][assetBLoop]))
         {
            Alert("Error calculating Correl Coeff between " + NettedAssets[assetALoop] + " and " + NettedAssets[assetBLoop] + " in: " + __FUNCTION__ + "()");
            return false;
         }
      }
   }

   //###########################
   //STEP 4: GET NOMINAL MONETARY VALUES FOR NETTED POSITIONS
   //###########################
   
   double nominalValues[];
   ArrayResize(nominalValues, ArraySize(NettedAssets));
   
   double portfolioNominalValue = 0.0;
   
   for(int assetLoop = 0; assetLoop < ArraySize(NettedAssets); assetLoop++)
   {
      //ASSET MONETARY VALUE (-VE IF NET SHORT, +VE IF NET LONG)
      double nominalValuePerUnitPerLot = SymbolInfoDouble(NettedAssets[assetLoop], SYMBOL_TRADE_TICK_VALUE) / SymbolInfoDouble(NettedAssets[assetLoop], SYMBOL_TRADE_TICK_SIZE);
      nominalValues[assetLoop] = NettedSizes[assetLoop] * nominalValuePerUnitPerLot * iClose(NettedAssets[assetLoop], PERIOD_M1, 0);
      
      //CALC 'PORTFOLIO' MONETARY VALUE
      portfolioNominalValue += MathAbs(nominalValues[assetLoop]);
      
      Print("💰 ", NettedAssets[assetLoop], " Net: ", DoubleToString(NettedSizes[assetLoop], 2), " lots = $", DoubleToString(MathAbs(nominalValues[assetLoop]), 0));
   }
   
   //##########################
   //STEP 5: CALCULATE POSITION WEIGHTS (Sum TO 1.0)
   //##########################
   
   double posWeight[];
   ArrayResize(posWeight, ArraySize(NettedAssets));
   
   for(int assetLoop = 0; assetLoop < ArraySize(NettedAssets); assetLoop++)
   {
      posWeight[assetLoop] = nominalValues[assetLoop] / portfolioNominalValue; //WEIGHTS WILL BE -VE FOR NET SHORT POSITIONS
   }
 
   //###########################
   //STEP 6: CALCULATE PORTFOLIO STD DEV USING NETTED POSITIONS
   //###########################

   double portSDCalcPart1 = 0.0;
   double portSDCalcPart2 = 0.0;
   
   //LOOP THROUGH ALL NETTED POSITIONS
   for(int assetALoop=0; assetALoop<ArraySize(NettedAssets); assetALoop++)
   {
      //INDIVIDUAL ASSET VARIANCE CONTRIBUTIONS
      portSDCalcPart1 += MathPow(posWeight[assetALoop], 2) * MathPow(stdDevReturns[assetALoop], 2);
      
      for(int assetBLoop=0; assetBLoop<ArraySize(NettedAssets); assetBLoop++)
      {
         //Only compare if not already compared the other way around (don't double count), and also don't compare a position with itself
         if(assetBLoop > assetALoop)
         {  
            portSDCalcPart2 +=     2 * posWeight[assetALoop] *                // Weight of net position A (keep sign)
                                       posWeight[assetBLoop] *                // Weight of net position B (keep sign)
                                       stdDevReturns[assetALoop] *            // Std Dev of Returns for position A's asset
                                       stdDevReturns[assetBLoop] *            // Std Dev of Returns for position B's asset
                                       correlCoeff[assetALoop][assetBLoop];   // Correlation between assets A and B
         }
      }
   }
   
   double portfolioStdDev = MathSqrt(portSDCalcPart1 + portSDCalcPart2);

   //######################################### 
   //STEP 7: CALCULATE THE VaR VALUE FOR NETTED PORTFOLIO
   //#########################################
  
   MultiPositionVaR = 1.65 * portfolioStdDev * portfolioNominalValue;
   
   Print("✅ Portfolio VaR calculated on ", ArraySize(NettedAssets), " netted positions: $", DoubleToString(MultiPositionVaR, 0));
   
   return true;
}

//###########################
// NEW FUNCTION: NET POSITIONS BY SYMBOL
//###########################
bool CPortfolioRiskMan::NetPositionsBySymbol(string &InputAssets[], double &InputSizes[], string &NetAssets[], double &NetSizes[])
{
   Print("🔄 Netting positions by symbol...");
   
   // Step 1: Find unique symbols and net their positions
   string UniqueSymbols[];
   double NetPositions[];
   int uniqueCount = 0;
   
   for(int i = 0; i < ArraySize(InputAssets); i++)
   {
      string currentSymbol = InputAssets[i];
      double currentSize = InputSizes[i];
      
      // Find if this symbol already exists in our unique list
      bool symbolExists = false;
      int existingIndex = -1;
      
      for(int j = 0; j < uniqueCount; j++)
      {
         if(UniqueSymbols[j] == currentSymbol)
         {
            symbolExists = true;
            existingIndex = j;
            break;
         }
      }
      
      if(symbolExists)
      {
         // Add to existing symbol's net position
         double oldNet = NetPositions[existingIndex];
         NetPositions[existingIndex] += currentSize;
         Print("📊 ", currentSymbol, ": ", DoubleToString(oldNet, 2), " + ", DoubleToString(currentSize, 2), " = ", DoubleToString(NetPositions[existingIndex], 2));
      }
      else
      {
         // New symbol - add to unique arrays
         ArrayResize(UniqueSymbols, uniqueCount + 1);
         ArrayResize(NetPositions, uniqueCount + 1);
         
         UniqueSymbols[uniqueCount] = currentSymbol;
         NetPositions[uniqueCount] = currentSize;
         uniqueCount++;
         Print("🆕 ", currentSymbol, ": ", DoubleToString(currentSize, 2), " (new symbol)");
      }
   }
   
   // Step 2: Filter out positions with zero net exposure
   int finalCount = 0;
   for(int i = 0; i < uniqueCount; i++)
   {
      if(MathAbs(NetPositions[i]) > 0.001) // Only include non-zero net positions
      {
         finalCount++;
      }
   }
   
   // Step 3: Build final arrays with only non-zero net positions
   ArrayResize(NetAssets, finalCount);
   ArrayResize(NetSizes, finalCount);
   
   int finalIndex = 0;
   for(int i = 0; i < uniqueCount; i++)
   {
      if(MathAbs(NetPositions[i]) > 0.001)
      {
         NetAssets[finalIndex] = UniqueSymbols[i];
         NetSizes[finalIndex] = NetPositions[i];
         
         string direction = (NetPositions[i] > 0) ? "LONG" : "SHORT";
         Print("✅ Final: ", UniqueSymbols[i], " = ", direction, " ", DoubleToString(MathAbs(NetPositions[i]), 2), " lots");
         
         finalIndex++;
      }
      else
      {
         Print("🔄 Excluded: ", UniqueSymbols[i], " (fully hedged - net = ", DoubleToString(NetPositions[i], 3), ")");
      }
   }
   
   Print("📋 Position netting complete: ", ArraySize(InputAssets), " positions → ", finalCount, " net positions");
   
   return true;
}

bool CPortfolioRiskMan::GetAssetStdDevReturns(string VolSymbolName, double &StandardDevOfReturns)
{
   double returns[];
   ArrayResize(returns, StandardDeviationPeriods);
                 
   //STORE 'CHANGE' IN CLOSE PRICES TO ARRAY
   for(int calcLoop=0; calcLoop < StandardDeviationPeriods; calcLoop++)
   {
      returns[calcLoop] = (iClose(VolSymbolName, ValueAtRiskTimeframe, calcLoop + 1) / iClose(VolSymbolName, ValueAtRiskTimeframe, calcLoop + 2)) - 1.0;
   }
   
   //CALCULATE THE STD DEV OF ALL RETURNS
   StandardDevOfReturns = MathStandardDeviation(returns);
   
   return true;
}

bool CPortfolioRiskMan::GetAssetCorrelation(string CorrSymbolName1, string CorrSymbolName2, double &PearsonR)
{
   //AVOID CURRENT BAR BECAUSE IT IS NOT YET COMPLETE
   int StartBar = 1; 
   
   //CALCULATE THE DIFF BETWEEN SUCCESSIVE VALUES IN EACH ARRAY
   double assetAPriceDiffValues[];
   double assetBPriceDiffValues[];
   
   ArrayResize(assetAPriceDiffValues, CorrelationPeriods);
   ArrayResize(assetBPriceDiffValues, CorrelationPeriods);
   
   int currBarAssetA = StartBar;
   int currBarAssetB = StartBar;
   int numBarsProcessed = 0;

   //SYNC BARS BETWEEN ASSETS
   while(numBarsProcessed < CorrelationPeriods)
   {
      //CHECK THAT EACH ASSET HAS DATA AVAILABLE
      if(iTime(CorrSymbolName1, ValueAtRiskTimeframe, currBarAssetA) == 0  ||  iTime(CorrSymbolName2, ValueAtRiskTimeframe, currBarAssetB) == 0)
      {
         PearsonR = 0.0;
         return false;
      }
      
      //SYNC THE BARS
      if(iTime(CorrSymbolName1, ValueAtRiskTimeframe, currBarAssetA) < iTime(CorrSymbolName2, ValueAtRiskTimeframe, currBarAssetB))
         currBarAssetB++;
      else if(iTime(CorrSymbolName1, ValueAtRiskTimeframe, currBarAssetA) > iTime(CorrSymbolName2, ValueAtRiskTimeframe, currBarAssetB))
         currBarAssetA++;
      else
      {
         //BARS SYNCED - CALCULATE PRICE CHANGES
         assetAPriceDiffValues[numBarsProcessed] = iClose(CorrSymbolName1, ValueAtRiskTimeframe, currBarAssetA) - iOpen(CorrSymbolName1, ValueAtRiskTimeframe, currBarAssetA);
         assetBPriceDiffValues[numBarsProcessed] = iClose(CorrSymbolName2, ValueAtRiskTimeframe, currBarAssetB) - iOpen(CorrSymbolName2, ValueAtRiskTimeframe, currBarAssetB);

         numBarsProcessed++;
         
         currBarAssetA++;
         currBarAssetB++;
      }
   }
         
   MathCorrelationPearson(assetAPriceDiffValues, assetBPriceDiffValues, PearsonR);

   return true;
}
Discover new MetaTrader 5 opportunities with MQL5 community and services
Discove

Discover new MetaTrader 5 opportunities with MQL5 community and services
Discover new MetaTrader 5 opportunities with MQL5 community and services
  • 2025.07.05
  • www.mql5.com
MQL5: language of trade strategies built-in the MetaTrader 5 Trading Platform, allows writing your own trading robots, technical indicators, scripts and libraries of functions