public void GetInjectWithdrawRange_ConstantInjectWithdrawRange_HasConstantRates() { CmdtyStorage <Day> cmdtyStorage = BuildCmdtyStorageWithAllConstantValues(); // TODO make parameterised var date = new Day(2019, 6, 1); double inventory = 548.54; InjectWithdrawRange injectWithdrawRange = cmdtyStorage.GetInjectWithdrawRange(date, inventory); Assert.Equal(ConstantMaxInjectRate, injectWithdrawRange.MaxInjectWithdrawRate); Assert.Equal(-ConstantMaxWithdrawRate, injectWithdrawRange.MinInjectWithdrawRate); }
GenerateValuationResults_CurrentPeriodEqualToStorageEndStorageInventoryHasTerminalValue(double startingInventory, double forwardPrice) { var storageStart = new Day(2019, 9, 1); var storageEnd = new Day(2019, 9, 30); TimeSeries <Month, Day> settlementDates = new TimeSeries <Month, Day> .Builder { { new Month(2019, 9), new Day(2019, 10, 5) } }.Build(); CmdtyStorage <Day> storage = CmdtyStorage <Day> .Builder .WithActiveTimePeriod(storageStart, storageEnd) .WithConstantInjectWithdrawRange(-45.5, 56.6) .WithConstantMinInventory(0.0) .WithConstantMaxInventory(1000.0) .WithPerUnitInjectionCost(0.8, injectionDate => injectionDate) .WithNoCmdtyConsumedOnInject() .WithPerUnitWithdrawalCost(1.2, withdrawalDate => withdrawalDate) .WithNoCmdtyConsumedOnWithdraw() .WithNoCmdtyInventoryLoss() .WithNoInventoryCost() .WithTerminalInventoryNpv((cmdtyPrice, terminalInventory) => cmdtyPrice * terminalInventory - 999.0) .Build(); var forwardCurve = new TimeSeries <Day, double> .Builder(1) { { storageEnd, forwardPrice } } .Build(); IntrinsicStorageValuationResults <Day> valuationResults = IntrinsicStorageValuation <Day> .ForStorage(storage) .WithStartingInventory(startingInventory) .ForCurrentPeriod(storageEnd) .WithForwardCurve(forwardCurve) .WithMonthlySettlement(settlementDates) .WithDiscountFactorFunc((valuationDate, cashFlowDate) => 1.0) // No discounting .WithFixedGridSpacing(10.0) .WithLinearInventorySpaceInterpolation() .WithNumericalTolerance(1E-10) .Calculate(); return(valuationResults); }
private static CmdtyStorage <Day> BuildCmdtyStorageWithAllConstantValues() { CmdtyStorage <Day> storage = CmdtyStorage <Day> .Builder .WithActiveTimePeriod(new Day(2019, 10, 1), new Day(2019, 11, 1)) .WithConstantInjectWithdrawRange(-ConstantMaxWithdrawRate, ConstantMaxInjectRate) .WithConstantMinInventory(ConstantMinInventory) .WithConstantMaxInventory(ConstantMaxInventory) .WithPerUnitInjectionCost(ConstantInjectionCost, injectionDate => injectionDate) .WithNoCmdtyConsumedOnInject() .WithPerUnitWithdrawalCost(ConstantWithdrawalCost, withdrawalDate => withdrawalDate) .WithNoCmdtyConsumedOnWithdraw() .WithNoCmdtyInventoryLoss() .WithNoInventoryCost() .MustBeEmptyAtEnd() .Build(); return(storage); }
Category = AddIn.ExcelFunctionCategory, IsThreadSafe = false, IsVolatile = false, IsExceptionSafe = true)] // TODO turn IsThreadSafe to true and use ConcurrentDictionary? public static object CreateStorage( [ExcelArgument(Name = "Storage_name", Description = "Name of storage object to create.")] string name, [ExcelArgument(Name = ExcelArg.StorageStart.Name, Description = ExcelArg.StorageStart.Description)] DateTime storageStart, [ExcelArgument(Name = ExcelArg.StorageEnd.Name, Description = ExcelArg.StorageEnd.Description)] DateTime storageEnd, [ExcelArgument(Name = ExcelArg.Ratchets.Name, Description = ExcelArg.Ratchets.Description)] object ratchets, [ExcelArgument(Name = ExcelArg.RatchetInterpolation.Name, Description = ExcelArg.RatchetInterpolation.Description)] string ratchetInterpolation, [ExcelArgument(Name = ExcelArg.InjectionCost.Name, Description = ExcelArg.InjectionCost.Description)] double injectionCostRate, [ExcelArgument(Name = ExcelArg.CmdtyConsumedInject.Name, Description = ExcelArg.CmdtyConsumedInject.Description)] double cmdtyConsumedOnInjection, [ExcelArgument(Name = ExcelArg.WithdrawalCost.Name, Description = ExcelArg.WithdrawalCost.Description)] double withdrawalCostRate, [ExcelArgument(Name = ExcelArg.CmdtyConsumedWithdraw.Name, Description = ExcelArg.CmdtyConsumedWithdraw.Description)] double cmdtyConsumedOnWithdrawal, [ExcelArgument(Name = ExcelArg.NumericalTolerance.Name, Description = ExcelArg.NumericalTolerance.Description)] object numericalToleranceIn) { return(StorageExcelHelper.ExecuteExcelFunction(() => { double numericalTolerance = StorageExcelHelper.DefaultIfExcelEmptyOrMissing(numericalToleranceIn, 1E-10, "Numerical_tolerance"); CmdtyStorage <Day> storage = StorageExcelHelper.CreateCmdtyStorageFromExcelInputs <Day>(storageStart, storageEnd, ratchets, ratchetInterpolation, injectionCostRate, cmdtyConsumedOnInjection, withdrawalCostRate, cmdtyConsumedOnWithdrawal, numericalTolerance); _storageObjects[name] = storage; return name; })); }
public static CmdtyStorage <T> CreateCmdtyStorageFromExcelInputs <T>(DateTime storageStartDateTime, DateTime storageEndDateTime, object injectWithdrawConstraintsIn, string injectWithdrawInterpolationIn, double injectionCostRate, double cmdtyConsumedOnInjection, double withdrawalCostRate, double cmdtyConsumedOnWithdrawal, double newtonRaphsonAccuracy) where T : ITimePeriod <T> { T storageStart = TimePeriodFactory.FromDateTime <T>(storageStartDateTime); T storageEnd = TimePeriodFactory.FromDateTime <T>(storageEndDateTime); if (injectWithdrawConstraintsIn is ExcelMissing || injectWithdrawConstraintsIn is ExcelEmpty) { throw new ArgumentException("Inject/withdraw constraints haven't been specified."); } if (!(injectWithdrawConstraintsIn is object[,] injectWithdrawArray)) { throw new ArgumentException("Inject/withdraw constraints have been incorrectly entered. Argument value should be of a range with 4 columns, the first containing dates, the rest containing numbers."); } if (injectWithdrawArray.GetLength(1) != 4) { throw new ArgumentException("Inject/withdraw constraints have been incorrectly entered. Argument value should be a range 4 columns."); } var injectWithdrawGrouped = TakeWhileNotEmptyOrError(injectWithdrawArray).Select((row, i) => new { period = ObjectToDateTime(row[0], $"Row {i + 1} of inject/withdraw/inventory constrains contains invalid date time in 1st column."), inventory = ObjectToDouble(row[1], $"Row {i + 1} of inject/withdraw/inventory constraints contains invalid inventory in 2nd column as is not a number."), injectRate = ObjectToDouble(row[2], $"Row {i + 1} of inject/withdraw/inventory constraints contains invalid injection rate in 3rd column as is not a number."), withdrawRate = ObjectToDouble(row[3], $"Row {i + 1} of inject/withdraw/inventory constraints contains invalid withdrawal in 4th column as is not a number.") }).GroupBy(arg => arg.period); var injectWithdrawConstraints = new List <InjectWithdrawRangeByInventoryAndPeriod <T> >(); foreach (var injectWithdrawGroup in injectWithdrawGrouped) { T period = TimePeriodFactory.FromDateTime <T>(injectWithdrawGroup.Key); IEnumerable <InjectWithdrawRangeByInventory> injectWithdrawByInventory = injectWithdrawGroup.Select(arg => new InjectWithdrawRangeByInventory(arg.inventory, new InjectWithdrawRange(-arg.withdrawRate, arg.injectRate))); injectWithdrawConstraints.Add(new InjectWithdrawRangeByInventoryAndPeriod <T>(period, injectWithdrawByInventory)); } InterpolationType interpolationType; if (injectWithdrawInterpolationIn == "PiecewiseLinear") { interpolationType = InterpolationType.PiecewiseLinear; } else if (injectWithdrawInterpolationIn == "Polynomial") { interpolationType = InterpolationType.PolynomialWithParams(newtonRaphsonAccuracy); } else { throw new ArgumentException($"Value of Inject_withdraw_interpolation '{injectWithdrawInterpolationIn}' not recognised. Must be either 'PiecewiseLinear' or 'Polynomial'."); } CmdtyStorage <T> storage = CmdtyStorage <T> .Builder .WithActiveTimePeriod(storageStart, storageEnd) .WithTimeAndInventoryVaryingInjectWithdrawRates(injectWithdrawConstraints, interpolationType) .WithPerUnitInjectionCost(injectionCostRate) .WithFixedPercentCmdtyConsumedOnInject(cmdtyConsumedOnInjection) .WithPerUnitWithdrawalCost(withdrawalCostRate) .WithFixedPercentCmdtyConsumedOnWithdraw(cmdtyConsumedOnWithdrawal) .WithNoCmdtyInventoryLoss() .WithNoInventoryCost() .MustBeEmptyAtEnd() .Build(); return(storage); }
Category = AddIn.ExcelFunctionCategory, IsThreadSafe = false, IsVolatile = false, IsExceptionSafe = true)] // TODO turn IsThreadSafe to true and use ConcurrentDictionary? public static object StorageValueThreeFactor( [ExcelArgument(Name = "Name", Description = "Name of cached object to create.")] string name, [ExcelArgument(Name = ExcelArg.StorageHandle.Name, Description = ExcelArg.StorageHandle.Description)] string storageHandle, [ExcelArgument(Name = ExcelArg.ValDate.Name, Description = ExcelArg.ValDate.Description)] DateTime valuationDate, [ExcelArgument(Name = ExcelArg.Inventory.Name, Description = ExcelArg.Inventory.Description)] double currentInventory, [ExcelArgument(Name = ExcelArg.ForwardCurve.Name, Description = ExcelArg.ForwardCurve.Description)] object forwardCurve, [ExcelArgument(Name = ExcelArg.InterestRateCurve.Name, Description = ExcelArg.InterestRateCurve.Description)] object interestRateCurve, [ExcelArgument(Name = ExcelArg.SpotVol.Name, Description = ExcelArg.SpotVol.Description)] double spotVol, [ExcelArgument(Name = ExcelArg.SpotMeanReversion.Name, Description = ExcelArg.SpotMeanReversion.Description)] double spotMeanReversion, [ExcelArgument(Name = ExcelArg.LongTermVol.Name, Description = ExcelArg.LongTermVol.Description)] double longTermVol, [ExcelArgument(Name = ExcelArg.SeasonalVol.Name, Description = ExcelArg.SeasonalVol.Description)] double seasonalVol, [ExcelArgument(Name = ExcelArg.DiscountDeltas.Name, Description = ExcelArg.DiscountDeltas.Description)] bool discountDeltas, [ExcelArgument(Name = ExcelArg.SettleDates.Name, Description = ExcelArg.SettleDates.Description)] object settleDatesIn, [ExcelArgument(Name = ExcelArg.NumSims.Name, Description = ExcelArg.NumSims.Description)] int numSims, [ExcelArgument(Name = ExcelArg.BasisFunctions.Name, Description = ExcelArg.BasisFunctions.Description)] string basisFunctionsIn, [ExcelArgument(Name = ExcelArg.Seed.Name, Description = ExcelArg.Seed.Description)] object seedIn, [ExcelArgument(Name = ExcelArg.ForwardSimSeed.Name, Description = ExcelArg.ForwardSimSeed.Description)] object fwdSimSeedIn, [ExcelArgument(Name = ExcelArg.NumGridPoints.Name, Description = ExcelArg.NumGridPoints.Description)] object numGlobalGridPointsIn, [ExcelArgument(Name = ExcelArg.NumericalTolerance.Name, Description = ExcelArg.NumericalTolerance.Description)] object numericalTolerance, [ExcelArgument(Name = ExcelArg.ExtraDecisions.Name, Description = ExcelArg.ExtraDecisions.Description)] object extraDecisions) { return(StorageExcelHelper.ExecuteExcelFunction(() => { _calcWrappers[name] = ExcelCalcWrapper.CreateCancellable((cancellationToken, onProgress) => { // TODO provide alternative method for interpolating interest rates Func <Day, double> interpolatedInterestRates = StorageExcelHelper.CreateLinearInterpolatedInterestRateFunc(interestRateCurve, ExcelArg.InterestRateCurve.Name); Func <Day, Day, double> discountFunc = StorageHelper.CreateAct65ContCompDiscounter(interpolatedInterestRates); Day valDate = Day.FromDateTime(valuationDate); Func <Day, Day> settleDateRule = StorageExcelHelper.CreateSettlementRule(settleDatesIn, ExcelArg.SettleDates.Name); CmdtyStorage <Day> storage = _storageObjects[storageHandle]; int numGlobalGridPoints = StorageExcelHelper.DefaultIfExcelEmptyOrMissing(numGlobalGridPointsIn, ExcelArg.NumGridPoints.Default, ExcelArg.NumGridPoints.Name); string basisFunctionsText = basisFunctionsIn.Replace("x_st", "x0").Replace("x_lt", "x1").Replace("x_sw", "x2"); var lsmcParamsBuilder = new LsmcValuationParameters <Day> .Builder { Storage = storage, CurrentPeriod = valDate, Inventory = currentInventory, ForwardCurve = StorageExcelHelper.CreateDoubleTimeSeries <Day>(forwardCurve, ExcelArg.ForwardCurve.Name), DiscountFactors = discountFunc, DiscountDeltas = discountDeltas, BasisFunctions = BasisFunctionsBuilder.Parse(basisFunctionsText), ExtraDecisions = StorageExcelHelper.DefaultIfExcelEmptyOrMissing(extraDecisions, 0, ExcelArg.ExtraDecisions.Name), CancellationToken = cancellationToken, OnProgressUpdate = onProgress, GridCalc = FixedSpacingStateSpaceGridCalc.CreateForFixedNumberOfPointsOnGlobalInventoryRange(storage, numGlobalGridPoints), NumericalTolerance = StorageExcelHelper.DefaultIfExcelEmptyOrMissing(numericalTolerance, LsmcValuationParameters <Day> .Builder.DefaultNumericalTolerance, ExcelArg.NumericalTolerance.Description), SettleDateRule = settleDateRule }; // TODO test that this works with expired storage Day endDate = new[] { valDate, storage.EndPeriod }.Max(); var threeFactorParams = MultiFactorParameters.For3FactorSeasonal(spotMeanReversion, spotVol, longTermVol, seasonalVol, valDate, endDate); // TODO better error messages if seedIn and fwdSimSeedIn cannot be cast int?seed = StorageExcelHelper.IsExcelEmptyOrMissing(seedIn) ? (int?)null : (int)(double)seedIn; int?fwdSimSeed = StorageExcelHelper.IsExcelEmptyOrMissing(fwdSimSeedIn) ? (int?)null : (int)(double)fwdSimSeedIn; lsmcParamsBuilder.SimulateWithMultiFactorModelAndMersenneTwister(threeFactorParams, numSims, seed, fwdSimSeed); return LsmcStorageValuation.WithNoLogger.Calculate(lsmcParamsBuilder.Build()); }); return name; })); }
static void Main(string[] args) { const double constantMaxInjectRate = 5.26; const double constantMaxWithdrawRate = 14.74; const double constantMaxInventory = 1100.74; const double constantMinInventory = 0.0; const double constantInjectionCost = 0.48; const double constantWithdrawalCost = 0.74; CmdtyStorage <Day> storage = CmdtyStorage <Day> .Builder .WithActiveTimePeriod(new Day(2019, 9, 1), new Day(2019, 10, 1)) .WithConstantInjectWithdrawRange(-constantMaxWithdrawRate, constantMaxInjectRate) .WithConstantMinInventory(constantMinInventory) .WithConstantMaxInventory(constantMaxInventory) .WithPerUnitInjectionCost(constantInjectionCost, injectionDate => injectionDate) .WithNoCmdtyConsumedOnInject() .WithPerUnitWithdrawalCost(constantWithdrawalCost, withdrawalDate => withdrawalDate) .WithNoCmdtyConsumedOnWithdraw() .WithNoCmdtyInventoryLoss() .WithNoCmdtyInventoryCost() .MustBeEmptyAtEnd() .Build(); var currentPeriod = new Day(2019, 9, 15); const double lowerForwardPrice = 56.6; const double forwardSpread = 87.81; double higherForwardPrice = lowerForwardPrice + forwardSpread; var forwardCurveBuilder = new TimeSeries <Day, double> .Builder(); foreach (var day in new Day(2019, 9, 15).EnumerateTo(new Day(2019, 9, 22))) { forwardCurveBuilder.Add(day, lowerForwardPrice); } foreach (var day in new Day(2019, 9, 23).EnumerateTo(new Day(2019, 10, 1))) { forwardCurveBuilder.Add(day, higherForwardPrice); } const double startingInventory = 50.0; IntrinsicStorageValuationResults <Day> valuationResults = IntrinsicStorageValuation <Day> .ForStorage(storage) .WithStartingInventory(startingInventory) .ForCurrentPeriod(currentPeriod) .WithForwardCurve(forwardCurveBuilder.Build()) .WithCmdtySettlementRule(day => day.First <Month>().Offset(1).First <Day>().Offset(5)) // Commodity is settled on the 5th day of the next month .WithDiscountFactorFunc(day => 1.0) // Assumes to discounting .WithFixedGridSpacing(10.0) .WithLinearInventorySpaceInterpolation() .WithNumericalTolerance(1E-12) .Calculate(); Console.WriteLine("Calculated intrinsic storage NPV: " + valuationResults.NetPresentValue.ToString("F2")); Console.WriteLine(); Console.WriteLine("Decision profile:"); Console.WriteLine(valuationResults.DecisionProfile.FormatData("F2", -1)); Console.WriteLine("Press any key to exit"); Console.ReadKey(); }