110 lines
4.9 KiB
C#
Executable File
110 lines
4.9 KiB
C#
Executable File
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<Contributions> 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<Contributions> binManager = new BinManager<Contributions>();
|
|
BinResult<Contributions> binResult=binManager.GetVaRReturn(weightedReturns,contributionsList,percentile);
|
|
Contributions contributions=binResult.Item;
|
|
if(null==contributions){varResult.Success=false;return varResult;}
|
|
Dictionary<String,PortfolioHolding> portfolioHoldingsBySymbol=new Dictionary<String,PortfolioHolding>();
|
|
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; }
|
|
}
|
|
}
|
|
}
|