public void Deconstruct(out DoubleCurve <T> curve, out IReadOnlyList <Contract <T> > bootstrappedContracts) { curve = Curve; bootstrappedContracts = BootstrappedContracts; }
public override DoubleCurve <TIndex> Build() { return(DoubleCurve.FromDictionary(Data, _weighting)); }
public BootstrapResults([NotNull] DoubleCurve <T> curve, [NotNull] List <Contract <T> > bootstrappedContracts) { Curve = curve ?? throw new ArgumentNullException(nameof(curve)); BootstrappedContracts = bootstrappedContracts ?? throw new ArgumentNullException(nameof(bootstrappedContracts)); }
// TODO include discount factors private static BootstrapResults <T> Calculate([NotNull] List <Contract <T> > contracts, [NotNull] Func <T, double> weighting, [NotNull] List <Shaping <T> > shapings, bool allowRedundancy = false) { if (contracts == null) { throw new ArgumentNullException(nameof(contracts)); } if (weighting == null) { throw new ArgumentNullException(nameof(weighting)); } if (shapings == null) { throw new ArgumentNullException(nameof(shapings)); } var contractsPlusShapingsCount = contracts.Count + shapings.Count; if (contractsPlusShapingsCount < 2) { throw new ArgumentException("contracts and shapings combined must contain at least two elements", nameof(contracts)); } // TODO check if two contracts have the same Start and End? var minTimePeriod = contracts.Select(contract => contract.Start) .Concat(shapings.Select(shaping => shaping.Start1)) .Concat(shapings.Select(shaping => shaping.Start2)) .Min(timePeriod => timePeriod); var maxTimePeriod = contracts.Select(contract => contract.End) .Concat(shapings.Select(shaping => shaping.End1)) .Concat(shapings.Select(shaping => shaping.End2)) .Max(timePeriod => timePeriod); var numTimePeriods = maxTimePeriod.OffsetFrom(minTimePeriod) + 1; var matrix = Matrix <double> .Build.Dense(contractsPlusShapingsCount, numTimePeriods); var vector = Vector <double> .Build.Dense(contractsPlusShapingsCount); for (int i = 0; i < contracts.Count; i++) { var contract = contracts[i]; double sumWeight = 0.0; var startOffset = contract.Start.OffsetFrom(minTimePeriod); var endOffset = contract.End.OffsetFrom(minTimePeriod); for (int j = startOffset; j <= endOffset; j++) { var timePeriod = minTimePeriod.Offset(j); var weight = weighting(timePeriod); matrix[i, j] = weight; sumWeight += weight; } if (sumWeight <= 0) { throw new ArgumentException( "sum of weighting evaluated to non-positive number for the following contract: " + contract); } vector[i] = contract.Price * sumWeight; } for (int i = 0; i < shapings.Count; i++) { var shaping = shapings[i]; int rowIndex = i + contracts.Count; var startOffset1 = shaping.Start1.OffsetFrom(minTimePeriod); var endOffset1 = shaping.End1.OffsetFrom(minTimePeriod); var startOffset2 = shaping.Start2.OffsetFrom(minTimePeriod); var endOffset2 = shaping.End2.OffsetFrom(minTimePeriod); double sumWeighting1 = 0.0; for (int j = startOffset1; j <= endOffset1; j++) { var timePeriod = minTimePeriod.Offset(j); var weight = weighting(timePeriod); matrix[rowIndex, j] = weight; sumWeighting1 += weight; } for (int j = startOffset1; j <= endOffset1; j++) { matrix[rowIndex, j] /= sumWeighting1; } double sumWeighting2 = shaping.Start2.EnumerateTo(shaping.End2).Sum(weighting); // TODO refactor to eliminate duplicate evaluation of weighting if (shaping.ShapingType == ShapingType.Spread) { // TODO refactor out common code to shared methods for (int j = startOffset2; j <= endOffset2; j++) { var timePeriod = minTimePeriod.Offset(j); var weight = weighting(timePeriod); matrix[rowIndex, j] -= weight / sumWeighting2; } vector[rowIndex] = shaping.Value; } else if (shaping.ShapingType == ShapingType.Ratio) { // TODO refactor out common code to shared methods for (int j = startOffset2; j <= endOffset2; j++) { var timePeriod = minTimePeriod.Offset(j); var weight = weighting(timePeriod); matrix[rowIndex, j] += -weight * shaping.Value / sumWeighting2; } vector[rowIndex] = 0.0; // Not necessary, but just being explicit } else { throw new InvalidEnumArgumentException($"shapings[{i}].ShapingType", (int)shaping.ShapingType, typeof(ShapingType)); // TODO check the exception message and whether InvalidEnumArgumentException should be used in this context } } if (!allowRedundancy) { var matrixRank = matrix.Rank(); if (matrixRank < matrix.RowCount) { throw new ArgumentException("Redundant contracts and shapings are present"); } } // Calculate least squares solution // TODO use alternative decomposition if full-rank http://www.imagingshop.com/linear-and-nonlinear-least-squares-with-math-net/ // TODO does Math.Net offer a double tolerance parameter like NMath Vector <double> leastSquaresSolution = matrix.Svd(true).Solve(vector); var curvePeriods = new List <T>(leastSquaresSolution.Count); var curvePrices = new List <double>(leastSquaresSolution.Count); // TODO pass raw results into BootstrapResults and have the processed result lazy calculated on access var bootstrappedContracts = new List <Contract <T> >(); // TODO check if only one element? var allOutputPeriods = minTimePeriod.EnumerateTo(maxTimePeriod.Offset(1)).Skip(1).ToList(); T contractStart = minTimePeriod; for (var i = 0; i < allOutputPeriods.Count; i++) { var outputPeriod = allOutputPeriods[i]; var inputStartsHere = contracts.Any(contract => contract.Start.Equals(outputPeriod)) || shapings.Any(shaping => shaping.Start1.Equals(outputPeriod) || shaping.Start2.Equals(outputPeriod)); var inputEndsOneStepBefore = contracts.Any(contract => contract.End.Next().Equals(outputPeriod)) || shapings.Any(shaping => shaping.End1.Next().Equals(outputPeriod) || shaping.End2.Next().Equals(outputPeriod)); // TODO refactor OR if (inputStartsHere || inputEndsOneStepBefore) // New output period { var contractEnd = outputPeriod.Previous(); // Calculate weighted average price // Add to bootstrappedContracts double price = WeightedAveragePrice(contractStart, contractEnd, minTimePeriod, leastSquaresSolution, weighting); bootstrappedContracts.Add(new Contract <T>(contractStart, contractEnd, price)); foreach (var period in contractStart.EnumerateTo(contractEnd)) { curvePeriods.Add(period); curvePrices.Add(price); } if (i < allOutputPeriods.Count - 1) { // Set contractStart unless last item of loop if (!IsGapInContractsAndShapings(contracts, shapings, outputPeriod)) { contractStart = outputPeriod; } else // outputPeriod is in a gap so need to increment i until not a gap { // TODO find more efficient way of doing this calculation do { curvePeriods.Add(outputPeriod); curvePrices.Add(price); i++; outputPeriod = outputPeriod.Next(); } while (IsGapInContractsAndShapings(contracts, shapings, outputPeriod)); contractStart = outputPeriod; // TODO remove top block of if as redundant? } } } } var curve = new DoubleCurve <T>(curvePeriods, curvePrices, weighting); return(new BootstrapResults <T>(curve, bootstrappedContracts)); }