Used ChatGPT to fix some issues with the VaR
This commit is contained in:
@@ -1,147 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MarketData.ValueAtRisk
|
||||
{
|
||||
public class BinResult<T>
|
||||
{
|
||||
public double Value{get;private set;}
|
||||
public T Item{get;private set;}
|
||||
public BinResult()
|
||||
public class BinResult<T>
|
||||
{
|
||||
}
|
||||
public BinResult(double value,T item)
|
||||
{
|
||||
this.Value=value;
|
||||
this.Item=item;
|
||||
}
|
||||
}
|
||||
public class BinManager<T>
|
||||
{
|
||||
private List<BinItem<T>> binItems;
|
||||
private long binCount;
|
||||
private long samples;
|
||||
public double Value { get; private set; }
|
||||
public T Item { get; private set; }
|
||||
|
||||
public BinManager(long binCount=100)
|
||||
{
|
||||
this.binCount = binCount;
|
||||
InitializeBins();
|
||||
}
|
||||
private void InitializeBins()
|
||||
{
|
||||
this.binItems = new List<BinItem<T>>();
|
||||
this.samples=0;
|
||||
}
|
||||
public BinResult<T> GetVaRReturn(double[] values,List<T> items,double percentile)
|
||||
{
|
||||
double minValue = float.NaN;
|
||||
double maxValue = float.NaN;
|
||||
double binSize = double.NaN;
|
||||
T item=default(T);
|
||||
public BinResult() { }
|
||||
|
||||
InitializeBins();
|
||||
GetMinMax(ref minValue,ref maxValue,values);
|
||||
binSize=(maxValue-minValue)/(double)binCount;
|
||||
for (double value = minValue; value < maxValue; value += binSize)
|
||||
{
|
||||
binItems.Add(new BinItem<T>(value));
|
||||
}
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
AddValueToBin(values[index]);
|
||||
AddObjectToBin(values[index],items[index]);
|
||||
}
|
||||
double samplesAtRank = samples-(samples * (percentile/100.00));
|
||||
if (samplesAtRank < 1) samplesAtRank = 1;
|
||||
long samplesInRank = 0;
|
||||
double percentVaR = 0;
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
samplesInRank += binItem.BinCount;
|
||||
percentVaR = binItem.BinValue;
|
||||
item=binItem.BinObject;
|
||||
if (samplesInRank > samplesAtRank) break;
|
||||
}
|
||||
return new BinResult<T>(percentVaR,item);
|
||||
public BinResult(double value, T item)
|
||||
{
|
||||
this.Value = value;
|
||||
this.Item = item;
|
||||
}
|
||||
}
|
||||
public double GetVaRReturn(double[] values,double percentile)
|
||||
{
|
||||
double minValue = float.NaN;
|
||||
double maxValue = float.NaN;
|
||||
double binSize = double.NaN;
|
||||
|
||||
InitializeBins();
|
||||
GetMinMax(ref minValue,ref maxValue,values);
|
||||
binSize=(maxValue-minValue)/(double)binCount;
|
||||
for (double value = minValue; value < maxValue; value += binSize)
|
||||
{
|
||||
binItems.Add(new BinItem<T>(value));
|
||||
}
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
AddValueToBin(values[index]);
|
||||
}
|
||||
double samplesAtRank = samples-(samples * (percentile/100.00));
|
||||
if (samplesAtRank < 1) samplesAtRank = 1;
|
||||
long samplesInRank = 0;
|
||||
double percentVaR = 0;
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
samplesInRank += binItem.BinCount;
|
||||
percentVaR = binItem.BinValue;
|
||||
if (samplesInRank > samplesAtRank) break;
|
||||
}
|
||||
return percentVaR;
|
||||
}
|
||||
private static void GetMinMax(ref double minValue,ref double maxValue,double[] values)
|
||||
public class BinManager<T>
|
||||
{
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
double value = values[index];
|
||||
if (0 == index)
|
||||
private List<BinItem<T>> binItems;
|
||||
private long binCount;
|
||||
private long samples;
|
||||
|
||||
public BinManager(long binCount = 100)
|
||||
{
|
||||
minValue = maxValue = value;
|
||||
continue;
|
||||
this.binCount = binCount;
|
||||
InitializeBins();
|
||||
}
|
||||
if (value > maxValue) maxValue = value;
|
||||
if (value < minValue) minValue = value;
|
||||
}
|
||||
}
|
||||
private void AddValueToBin(double value)
|
||||
{
|
||||
bool added = false;
|
||||
samples++;
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
if (value <= binItem.BinValue)
|
||||
|
||||
private void InitializeBins()
|
||||
{
|
||||
binItem.BinCount++;
|
||||
added = true;
|
||||
break;
|
||||
this.binItems = new List<BinItem<T>>();
|
||||
this.samples = 0;
|
||||
}
|
||||
}
|
||||
if (false == added && binItems.Count>0) binItems[binItems.Count - 1].BinCount++;
|
||||
}
|
||||
private void AddObjectToBin(double value,T item)
|
||||
{
|
||||
bool added = false;
|
||||
// samples++;
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
if (value <= binItem.BinValue)
|
||||
|
||||
public BinResult<T> GetVaRReturn(double[] values, List<T> items, double percentile)
|
||||
{
|
||||
binItem.BinObject=item;
|
||||
added = true;
|
||||
break;
|
||||
double minValue = float.NaN;
|
||||
double maxValue = float.NaN;
|
||||
double binSize = double.NaN;
|
||||
T item = default(T);
|
||||
|
||||
InitializeBins();
|
||||
GetMinMax(ref minValue, ref maxValue, values);
|
||||
|
||||
binSize = (maxValue - minValue) / (double)binCount;
|
||||
if (binSize == 0) binSize = 1e-10;
|
||||
|
||||
// Create bins
|
||||
for (double value = minValue; value <= maxValue; value += binSize)
|
||||
{
|
||||
binItems.Add(new BinItem<T>(value));
|
||||
}
|
||||
|
||||
// Add values and objects to bins
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
AddValueToBin(values[index]);
|
||||
AddObjectToBin(values[index], items[index]);
|
||||
}
|
||||
|
||||
double samplesAtRank = samples - (samples * (percentile / 100.0));
|
||||
if (samplesAtRank < 1) samplesAtRank = 1;
|
||||
|
||||
long samplesInRank = 0;
|
||||
double percentVaR = 0;
|
||||
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
samplesInRank += binItem.BinCount;
|
||||
percentVaR = binItem.BinValue;
|
||||
item = binItem.BinObject;
|
||||
|
||||
if (samplesInRank > samplesAtRank) break;
|
||||
}
|
||||
|
||||
return new BinResult<T>(percentVaR, item);
|
||||
}
|
||||
|
||||
public double GetVaRReturn(double[] values, double percentile)
|
||||
{
|
||||
double minValue = float.NaN;
|
||||
double maxValue = float.NaN;
|
||||
double binSize = double.NaN;
|
||||
|
||||
InitializeBins();
|
||||
GetMinMax(ref minValue, ref maxValue, values);
|
||||
|
||||
binSize = (maxValue - minValue) / (double)binCount;
|
||||
if (binSize == 0) binSize = 1e-10;
|
||||
|
||||
// Create bins
|
||||
for (double value = minValue; value <= maxValue; value += binSize)
|
||||
{
|
||||
binItems.Add(new BinItem<T>(value));
|
||||
}
|
||||
|
||||
// Add values to bins
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
AddValueToBin(values[index]);
|
||||
}
|
||||
|
||||
double samplesAtRank = samples - (samples * (percentile / 100.0));
|
||||
if (samplesAtRank < 1) samplesAtRank = 1;
|
||||
|
||||
long samplesInRank = 0;
|
||||
double percentVaR = 0;
|
||||
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
samplesInRank += binItem.BinCount;
|
||||
percentVaR = binItem.BinValue;
|
||||
|
||||
if (samplesInRank > samplesAtRank) break;
|
||||
}
|
||||
|
||||
return percentVaR;
|
||||
}
|
||||
|
||||
private static void GetMinMax(ref double minValue, ref double maxValue, double[] values)
|
||||
{
|
||||
for (int index = 0; index < values.Length; index++)
|
||||
{
|
||||
double value = values[index];
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
minValue = maxValue = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value > maxValue) maxValue = value;
|
||||
if (value < minValue) minValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValueToBin(double value)
|
||||
{
|
||||
bool added = false;
|
||||
samples++;
|
||||
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
if (value <= binItem.BinValue)
|
||||
{
|
||||
binItem.BinCount++;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added && binItems.Count > 0)
|
||||
binItems[binItems.Count - 1].BinCount++;
|
||||
}
|
||||
|
||||
private void AddObjectToBin(double value, T item)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
for (int index = 0; index < binItems.Count; index++)
|
||||
{
|
||||
BinItem<T> binItem = binItems[index];
|
||||
if (value <= binItem.BinValue)
|
||||
{
|
||||
binItem.BinObject = item;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!added && binItems.Count > 0)
|
||||
binItems[binItems.Count - 1].BinObject = item;
|
||||
}
|
||||
}
|
||||
if (false == added && binItems.Count>0) binItems[binItems.Count - 1].BinObject=item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
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 (null == portfolioHoldings || 0 == portfolioHoldings.Count)
|
||||
{
|
||||
varResult.Success=false;
|
||||
return varResult;
|
||||
}
|
||||
// This ensures that the pricing information for each holding is symmetric.
|
||||
// The piece of code that checks for jagged pricing should be handled differently. For example, we should be able to continue the VaR analysis and simply account for no exposure to the
|
||||
// given symbol in the event of a lack of pricing data. This would handle the current issue with securities that have been trading for less than the number of observation days.
|
||||
if (portfolioHoldings.Count > 1)
|
||||
{
|
||||
int priceCount=-1;
|
||||
for (int index = 1; index < portfolioHoldings.Count; index++)
|
||||
private HistoricalVaR() { }
|
||||
|
||||
public static VaRResult GetVaR(PortfolioHoldings portfolioHoldings, double percentile, int returnDays = 1)
|
||||
{
|
||||
if(portfolioHoldings[index].Prices.Count>priceCount)priceCount=portfolioHoldings[index].Prices.Count;
|
||||
VaRResult varResult = new VaRResult();
|
||||
|
||||
// Validate portfolio
|
||||
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
|
||||
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 holdings weightings
|
||||
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 weighted returns for the observation period
|
||||
portfolioHoldings.SetReturnDays(returnDays);
|
||||
int numReturns = portfolioHoldings.Min(p => p.Returns.Length);
|
||||
|
||||
WeightedReturnsWithContribution weightedReturnsWithContribution = 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
|
||||
);
|
||||
|
||||
weightedReturnsWithContribution.Add(index, weightedReturn);
|
||||
}
|
||||
}
|
||||
|
||||
double[] weightedReturns = weightedReturnsWithContribution.GetWeightedReturns();
|
||||
List<Contributions> contributionsList = weightedReturnsWithContribution.GetContributions();
|
||||
|
||||
// Organize the weighted returns into bins for percentile access
|
||||
BinManager<Contributions> binManager = new BinManager<Contributions>();
|
||||
BinResult<Contributions> binResult = binManager.GetVaRReturn(weightedReturns, contributionsList, percentile);
|
||||
Contributions contributions = binResult.Item;
|
||||
|
||||
if (contributions == null)
|
||||
{
|
||||
varResult.Success = false;
|
||||
return varResult;
|
||||
}
|
||||
|
||||
// Map contributions back to portfolio holdings
|
||||
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);
|
||||
}
|
||||
for (int index = 1; index < portfolioHoldings.Count; index++)
|
||||
|
||||
public int ReturnDays
|
||||
{
|
||||
if (portfolioHoldings[index].Prices.Count != priceCount)
|
||||
{
|
||||
varResult.Success=false;
|
||||
varResult.Message=String.Format("Insufficient price history for {0}. {1}/{2}",portfolioHoldings[index].Symbol,portfolioHoldings[index].Prices.Count,priceCount);
|
||||
return varResult;
|
||||
}
|
||||
get => returnDays;
|
||||
set => returnDays = value;
|
||||
}
|
||||
}
|
||||
// Calculate total market value and then calculate the weightings of each holding
|
||||
double marketValue = portfolioHoldings.GetMarketValue();
|
||||
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;
|
||||
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, portfolioHoldings.GetMarketValue() * binResult.Value);
|
||||
}
|
||||
public int ReturnDays
|
||||
{
|
||||
get { return returnDays; }
|
||||
set { returnDays = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +126,6 @@ namespace MarketData.ValueAtRisk
|
||||
}
|
||||
public void SetReturnDays(int days)
|
||||
{
|
||||
if (returnDays == days) return;
|
||||
returnDays = days;
|
||||
returns = prices.GetReturnsAsDoubleArray(returnDays);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user