private static Matrix <double> CalcFactorCovarianceSquareRoots(MultiFactorParameters <T> modelParameters, double timeIncrement) { int numFactors = modelParameters.NumFactors; // TODO does Math.NET have a more efficient representation of symmetric and triangular matrices? Matrix <double> covarianceMatrix = Matrix <double> .Build.Dense(numFactors, numFactors); for (int i = 0; i < numFactors; i++) { for (int j = i; j < numFactors; j++) { double meanReversionsSum = modelParameters.Factors[i].MeanReversion + modelParameters.Factors[j].MeanReversion; double covariance = modelParameters.FactorCorrelations[i, j] * CalcFactorContExt(meanReversionsSum, timeIncrement); covarianceMatrix[i, j] = covariance; if (i != j) { covarianceMatrix[j, i] = covariance; } } } return(covarianceMatrix.Cholesky().Factor); }
private static double CalcDriftAdjustment(MultiFactorParameters <T> modelParameters, double timeToMaturity, T period) { double sum = 0.0; // TODO use symmetry to speed up? // TODO formulate as matrix multiplication as quadratic form? for (int i = 0; i < modelParameters.NumFactors; i++) { Factor <T> factor1 = modelParameters.Factors[i]; if (!factor1.Volatility.TryGetValue(period, out double vol1)) { throw new ArgumentException($"Factor {i} of multi-factor model parameters does not contain vol for period {period}.", nameof(modelParameters)); } double meanReversion1 = factor1.MeanReversion; for (int j = 0; j < modelParameters.NumFactors; j++) { Factor <T> factor2 = modelParameters.Factors[j]; if (!factor2.Volatility.TryGetValue(period, out double vol2)) { throw new ArgumentException($"Factor {j} of multi-factor model parameters does not contain vol for period {period}.", nameof(modelParameters)); } double meanReversion2 = factor2.MeanReversion; sum += vol1 * vol2 * modelParameters.FactorCorrelations[i, j] * CalcFactorContExt(meanReversion1 + meanReversion2, timeToMaturity); } } return(-sum / 2.0); }
public MultiFactorSpotPriceSimulator([NotNull] MultiFactorParameters <T> modelParameters, DateTime currentDateTime, [NotNull] IReadOnlyDictionary <T, double> forwardCurve, [NotNull] IEnumerable <T> simulatedPeriods, Func <DateTime, DateTime, double> timeFunc, [NotNull] IStandardNormalGenerator standardNormalGenerator) // TODO pass in random number generator factory { if (modelParameters == null) { throw new ArgumentNullException(nameof(modelParameters)); } if (forwardCurve == null) { throw new ArgumentNullException(nameof(forwardCurve)); } if (simulatedPeriods == null) { throw new ArgumentNullException(nameof(simulatedPeriods)); } _standardNormalGenerator = standardNormalGenerator ?? throw new ArgumentNullException(nameof(standardNormalGenerator)); T[] simulatedPeriodsArray = simulatedPeriods.ToArray(); int numPeriods = simulatedPeriodsArray.Length; int numFactors = modelParameters.NumFactors; if (numPeriods == 0) { throw new ArgumentException(nameof(simulatedPeriods) + " argument cannot be empty.", nameof(simulatedPeriods)); } int numRandomDimensions = numPeriods * numFactors; if (!_standardNormalGenerator.MatchesDimensions(numRandomDimensions)) { throw new ArgumentException($"Injected normal random generator is not set up to generate with {numRandomDimensions} dimensions.", nameof(standardNormalGenerator)); } _forwardPrices = new double[numPeriods]; _driftAdjustments = new double[numPeriods]; _reversionMultipliers = new double[numPeriods, numFactors]; _spotVols = new double[numPeriods, numFactors]; _factorCovarianceSquareRoots = new Matrix <double> [numPeriods]; _simulatedPeriods = simulatedPeriodsArray; double timeToMaturityPrevious = 0; for (int i = 0; i < simulatedPeriodsArray.Length; i++) { T period = simulatedPeriodsArray[i]; if (period.Start <= currentDateTime) // TODO make comparison using day count function? { throw new ArgumentException($"All elements of {simulatedPeriods} must start after {currentDateTime}.", nameof(simulatedPeriods)); } if (i > 0) { T lastPeriod = simulatedPeriodsArray[i - 1]; if (period.CompareTo(lastPeriod) < 0) { throw new ArgumentException(nameof(simulatedPeriods) + " argument must be sorted in ascending order.", nameof(simulatedPeriods)); } if (period.Equals(lastPeriod)) { throw new ArgumentException(nameof(simulatedPeriods) + $" argument cannot contain duplicated elements. More than one element with value {period} found.", nameof(simulatedPeriods)); } } if (!forwardCurve.TryGetValue(period, out double forwardPrice)) { throw new ArgumentException($"Forward curve does not contain price for period {period}.", nameof(forwardCurve)); } _forwardPrices[i] = forwardPrice; double timeToMaturity = timeFunc(currentDateTime, period.Start); double timeIncrement = timeToMaturity - timeToMaturityPrevious; for (int j = 0; j < numFactors; j++) { Factor <T> factor = modelParameters.Factors[j]; _spotVols[i, j] = factor.Volatility[period]; // TODO check if period in volatility _reversionMultipliers[i, j] = Math.Exp(-factor.MeanReversion * timeIncrement); } _factorCovarianceSquareRoots[i] = CalcFactorCovarianceSquareRoots(modelParameters, timeIncrement); _driftAdjustments[i] = CalcDriftAdjustment(modelParameters, timeToMaturity, period); timeToMaturityPrevious = timeToMaturity; } }