using System; using System.Collections.Generic; using System.Linq; using System.Text; using MarketData.DataAccess; using MarketData.Utils; namespace MarketData.ValueAtRisk { public class HistoricalVaR { private int returnDays = 1; private HistoricalVaR() { } public static VaRResult GetVaR(PortfolioHoldings portfolioHoldings, double percentile,int returnDays=1) { VaRResult varResult=new VaRResult(); if (portfolioHoldings == null || portfolioHoldings.Count == 0) { varResult.Success = false; varResult.Message = "Portfolio is null or empty."; return varResult; } // Determine the minimum common price history across holdings int minPriceCount = int.MaxValue; for (int i = 0; i < portfolioHoldings.Count; i++) { int count = portfolioHoldings[i].Prices.Count; if (count < minPriceCount) minPriceCount = count; } // Enforce minimum observation requirement (optional but recommended) if (minPriceCount < 30) // or whatever threshold you prefer { varResult.Success = false; varResult.Message = $"Insufficient common price history ({minPriceCount} observations)."; return varResult; } // Truncate all holdings to the common window for (int i = 0; i < portfolioHoldings.Count; i++) { if (portfolioHoldings[i].Prices.Count > minPriceCount) { portfolioHoldings[i].Prices = new MarketDataModel.Prices(portfolioHoldings[i].Prices.Skip(portfolioHoldings[i].Prices.Count - minPriceCount).ToList()); } } // Calculate total market value and then calculate the weightings of each holding double marketValue = portfolioHoldings.GetMarketValue(); if (marketValue == 0) { varResult.Success = false; varResult.Message = "Portfolio market value is zero."; return varResult; } for (int index = 0; index < portfolioHoldings.Count; index++) { PortfolioHolding portfolioHolding = portfolioHoldings[index]; portfolioHolding.Weight = portfolioHolding.MarketValue / marketValue; } // Calculate the weighted returns for the observation period portfolioHoldings.SetReturnDays(returnDays); // int numReturns=portfolioHoldings[0].Returns.Length; int numReturns = portfolioHoldings.Min(p => p.Returns.Length); WeightedReturnsWithContribution weightedReturnsWithContrbution=new WeightedReturnsWithContribution(); for (int index = 0; index < numReturns; index++) { for (int portfolioIndex = 0; portfolioIndex < portfolioHoldings.Count; portfolioIndex++) { PortfolioHolding portfolioHolding = portfolioHoldings[portfolioIndex]; WeightedReturn weightedReturn=new WeightedReturn(portfolioHolding.Symbol,portfolioHolding.Prices[index].Date,portfolioHolding.Returns[index] * portfolioHolding.Weight); weightedReturnsWithContrbution.Add(index,weightedReturn); } } double[] weightedReturns=weightedReturnsWithContrbution.GetWeightedReturns(); List contributionsList=weightedReturnsWithContrbution.GetContributions(); // Organize the weighted returns into bins so we can access the nth percentile rank // The VaR nth percentile VaR is simply the weighted return at the given rank (i.e.) 90%, 95%, 99% within the bin // The VaR result will be a negative percent and negative amount to show that this is a loss. We display this as a positive number so we need to take the absolute value of the result // ** To Do ** bring out the contributors to VaR at the given rank. BinManager binManager = new BinManager(); BinResult binResult=binManager.GetVaRReturn(weightedReturns,contributionsList,percentile); Contributions contributions=binResult.Item; if(null==contributions){varResult.Success=false;return varResult;} Dictionary portfolioHoldingsBySymbol=new Dictionary(); foreach(PortfolioHolding portfolioHolding in portfolioHoldings){if(!portfolioHoldingsBySymbol.ContainsKey(portfolioHolding.Symbol))portfolioHoldingsBySymbol.Add(portfolioHolding.Symbol,portfolioHolding);} foreach(Contribution contribution in contributions) { if(!portfolioHoldingsBySymbol.ContainsKey(contribution.Symbol))continue; portfolioHoldingsBySymbol[contribution.Symbol].Contribution=contribution.ContributionValue; portfolioHoldingsBySymbol[contribution.Symbol].ContributionDate=contribution.AnalysisDate; } return new VaRResult(binResult.Value, marketValue * binResult.Value); } public int ReturnDays { get { return returnDays; } set { returnDays = value; } } } }