private static (double StorageNpv, double CmdtyConsumed, double PeriodPv) StorageValueForDecision(
            ICmdtyStorage <T> storage, T period, double inventory, double inventoryLoss,
            double injectWithdrawVolume, double cmdtyPrice, Func <double, double> continuationValueInterpolated,
            double discountFactorFromCmdtySettlement, Func <Day, double> discountFactors)
        {
            double inventoryAfterDecision = inventory + injectWithdrawVolume - inventoryLoss;
            double continuationFutureNpv  = continuationValueInterpolated(inventoryAfterDecision);
            // TODO use StorageHelper.StorageImmediateNpvForDecision

            double injectWithdrawNpv = -injectWithdrawVolume * cmdtyPrice * discountFactorFromCmdtySettlement;

            IReadOnlyList <DomesticCashFlow> inventoryCostCashFlows = storage.CmdtyInventoryCost(period, inventory);
            double inventoryCostNpv = inventoryCostCashFlows.Sum(cashFlow => cashFlow.Amount * discountFactors(cashFlow.Date));

            IReadOnlyList <DomesticCashFlow> decisionCostCashFlows = injectWithdrawVolume > 0.0
                    ? storage.InjectionCost(period, inventory, injectWithdrawVolume)
                    : storage.WithdrawalCost(period, inventory, -injectWithdrawVolume);

            double decisionCostNpv = decisionCostCashFlows.Sum(cashFlow => cashFlow.Amount * discountFactors(cashFlow.Date));

            double cmdtyUsedForInjectWithdrawVolume = injectWithdrawVolume > 0.0
                ? storage.CmdtyVolumeConsumedOnInject(period, inventory, injectWithdrawVolume)
                : storage.CmdtyVolumeConsumedOnWithdraw(period, inventory, Math.Abs(injectWithdrawVolume));

            // Note that calculations assume that decision volumes do NOT include volumes consumed, and that these volumes are purchased in the market
            double cmdtyUsedForInjectWithdrawNpv = -cmdtyUsedForInjectWithdrawVolume * cmdtyPrice * discountFactorFromCmdtySettlement;

            double periodPv   = injectWithdrawNpv - decisionCostNpv + cmdtyUsedForInjectWithdrawNpv - inventoryCostNpv;
            double storageNpv = continuationFutureNpv + periodPv;

            return(StorageNpv : storageNpv, CmdtyConsumed : cmdtyUsedForInjectWithdrawVolume, PeriodPv : periodPv);
        }
        OptimalDecisionAndValue(ICmdtyStorage <T> storage, T period, double inventory,
                                double nextStepInventorySpaceMin, double nextStepInventorySpaceMax, double cmdtyPrice,
                                Func <double, double> continuationValueByInventory, double discountFactorFromCmdtySettlement,
                                Func <Day, double> discountFactors, double numericalTolerance)
        {
            InjectWithdrawRange injectWithdrawRange = storage.GetInjectWithdrawRange(period, inventory);
            double inventoryLoss = storage.CmdtyInventoryPercentLoss(period) * inventory;

            double[] decisionSet = StorageHelper.CalculateBangBangDecisionSet(injectWithdrawRange, inventory, inventoryLoss,
                                                                              nextStepInventorySpaceMin, nextStepInventorySpaceMax, numericalTolerance);
            var valuesForDecision        = new double[decisionSet.Length];
            var cmdtyConsumedForDecision = new double[decisionSet.Length];
            var periodPvForDecision      = new double[decisionSet.Length];

            for (var j = 0; j < decisionSet.Length; j++)
            {
                double decisionInjectWithdraw = decisionSet[j];
                (valuesForDecision[j], cmdtyConsumedForDecision[j], periodPvForDecision[j]) = StorageValueForDecision(storage, period, inventory, inventoryLoss,
                                                                                                                      decisionInjectWithdraw, cmdtyPrice, continuationValueByInventory, discountFactorFromCmdtySettlement, discountFactors);
            }

            (double storageNpv, int indexOfOptimalDecision) = StorageHelper.MaxValueAndIndex(valuesForDecision);

            return(StorageNpv : storageNpv, OptimalInjectWithdraw : decisionSet[indexOfOptimalDecision],
                   CmdtyConsumedOnAction : cmdtyConsumedForDecision[indexOfOptimalDecision],
                   InventoryLoss : inventoryLoss, PeriodPv : periodPvForDecision[indexOfOptimalDecision]);
        }
Beispiel #3
0
 private LsmcValuationParameters(T currentPeriod, double inventory, TimeSeries <T, double> forwardCurve,
                                 ICmdtyStorage <T> storage, Func <T, Day> settleDateRule, Func <Day, Day, double> discountFactors, IDoubleStateSpaceGridCalc gridCalc,
                                 double numericalTolerance, SimulateSpotPrice regressionSpotSims, SimulateSpotPrice valuationSpotSims, IEnumerable <BasisFunction> basisFunctions,
                                 CancellationToken cancellationToken, bool discountDeltas, int extraDecisions, Action <double> onProgressUpdate = null)
 {
     CurrentPeriod               = currentPeriod;
     Inventory                   = inventory;
     ForwardCurve                = forwardCurve;
     Storage                     = storage;
     SettleDateRule              = settleDateRule;
     DiscountFactors             = discountFactors;
     GridCalc                    = gridCalc;
     NumericalTolerance          = numericalTolerance;
     RegressionSpotSimsGenerator = () => regressionSpotSims(CurrentPeriod, storage.StartPeriod, storage.EndPeriod, forwardCurve);
     ValuationSpotSimsGenerator  = () => valuationSpotSims(CurrentPeriod, storage.StartPeriod, storage.EndPeriod, forwardCurve);
     BasisFunctions              = basisFunctions.ToArray();
     CancellationToken           = cancellationToken;
     DiscountDeltas              = discountDeltas;
     ExtraDecisions              = extraDecisions;
     OnProgressUpdate            = onProgressUpdate;
 }
        public static FixedSpacingStateSpaceGridCalc CreateForFixedNumberOfPointsOnGlobalInventoryRange <T>(
            [NotNull] ICmdtyStorage <T> storage,
            int numGridPointsOverGlobalInventoryRange)
            where T : ITimePeriod <T>
        {
            if (storage == null)
            {
                throw new ArgumentNullException(nameof(storage));
            }
            if (numGridPointsOverGlobalInventoryRange < 3)
            {
                throw new ArgumentException($"Parameter {nameof(numGridPointsOverGlobalInventoryRange)} value must be at least 3.", nameof(numGridPointsOverGlobalInventoryRange));
            }

            T[] storagePeriods = storage.StartPeriod.EnumerateTo(storage.EndPeriod).ToArray();

            double globalMaxInventory = storagePeriods.Max(storage.MaxInventory);
            double globalMinInventory = storagePeriods.Min(storage.MinInventory);
            double gridSpacing        = (globalMaxInventory - globalMinInventory) /
                                        (numGridPointsOverGlobalInventoryRange - 1);

            return(new FixedSpacingStateSpaceGridCalc(gridSpacing));
        }
Beispiel #5
0
        public static TimeSeries <T, InventoryRange> CalculateInventorySpace <T>(ICmdtyStorage <T> storage, double startingInventory, T currentPeriod)
            where T : ITimePeriod <T>
        {
            if (currentPeriod.CompareTo(storage.EndPeriod) > 0)     // TODO should condition be >= 0?
            {
                throw new ArgumentException("Storage has expired"); // TODO change to return empty TimeSeries?
            }
            T startActiveStorage = storage.StartPeriod.CompareTo(currentPeriod) > 0 ? storage.StartPeriod : currentPeriod;

            int numPeriods = storage.EndPeriod.OffsetFrom(startActiveStorage);

            // Calculate the inventory space range going forward

            var forwardCalcMaxInventory = new double[numPeriods];
            var forwardCalcMinInventory = new double[numPeriods];

            double minInventoryForwardCalc = startingInventory;
            double maxInventoryForwardCalc = startingInventory;

            for (int i = 0; i < numPeriods; i++)
            {
                T      periodLoop           = startActiveStorage.Offset(i);
                T      nextPeriod           = periodLoop.Offset(1);
                double inventoryPercentLoss = storage.CmdtyInventoryPercentLoss(periodLoop);

                double injectWithdrawMin  = storage.GetInjectWithdrawRange(periodLoop, minInventoryForwardCalc).MinInjectWithdrawRate;
                double inventoryLossAtMin = inventoryPercentLoss * minInventoryForwardCalc;
                double storageMin         = storage.MinInventory(nextPeriod);
                minInventoryForwardCalc    = Math.Max(minInventoryForwardCalc - inventoryLossAtMin + injectWithdrawMin, storageMin);
                forwardCalcMinInventory[i] = minInventoryForwardCalc;

                double injectWithdrawMax  = storage.GetInjectWithdrawRange(periodLoop, maxInventoryForwardCalc).MaxInjectWithdrawRate;
                double inventoryLossAtMax = inventoryPercentLoss * maxInventoryForwardCalc;
                double storageMax         = storage.MaxInventory(nextPeriod);
                maxInventoryForwardCalc    = Math.Min(maxInventoryForwardCalc - inventoryLossAtMax + injectWithdrawMax, storageMax);
                forwardCalcMaxInventory[i] = maxInventoryForwardCalc;
            }

            // Calculate the inventory space range going backwards
            var backwardCalcMaxInventory = new double[numPeriods];

            var backwardCalcMinInventory = new double[numPeriods];

            T periodBackLoop = storage.EndPeriod;

            backwardCalcMaxInventory[numPeriods - 1] = storage.MustBeEmptyAtEnd ? 0 : storage.MaxInventory(storage.EndPeriod);
            backwardCalcMinInventory[numPeriods - 1] = storage.MustBeEmptyAtEnd ? 0 : storage.MinInventory(storage.EndPeriod);

            for (int i = numPeriods - 2; i >= 0; i--)
            {
                periodBackLoop = periodBackLoop.Offset(-1);
                backwardCalcMaxInventory[i] = storage.InventorySpaceUpperBound(periodBackLoop, backwardCalcMinInventory[i + 1], backwardCalcMaxInventory[i + 1]);
                backwardCalcMinInventory[i] = storage.InventorySpaceLowerBound(periodBackLoop, backwardCalcMinInventory[i + 1],
                                                                               backwardCalcMaxInventory[i + 1]);
            }

            // Calculate overall inventory space and check for consistency

            var inventoryRanges = new InventoryRange[numPeriods];

            for (int i = 0; i < numPeriods; i++)
            {
                double inventorySpaceMax = Math.Min(forwardCalcMaxInventory[i], backwardCalcMaxInventory[i]);
                double inventorySpaceMin = Math.Max(forwardCalcMinInventory[i], backwardCalcMinInventory[i]);
                if (inventorySpaceMin > inventorySpaceMax)
                {
                    throw new InventoryConstraintsCannotBeFulfilledException();
                }
                inventoryRanges[i] = new InventoryRange(inventorySpaceMin, inventorySpaceMax);
            }

            return(new TimeSeries <T, InventoryRange>(startActiveStorage.Offset(1), inventoryRanges));
        }
 public static ITreeAddStartingInventory <T> ForStorage([NotNull] ICmdtyStorage <T> storage)
 {
     return(new TreeStorageValuation <T>(storage));
 }
 private TreeStorageValuation([NotNull] ICmdtyStorage <T> storage)
 {
     _storage = storage ?? throw new ArgumentNullException(nameof(storage));
 }
 public static IIntrinsicAddStartingInventory <T> ForStorage([NotNull] ICmdtyStorage <T> storage)
 {
     return(new IntrinsicStorageValuation <T>(storage));
 }
        private static IntrinsicStorageValuationResults <T> Calculate(T currentPeriod, double startingInventory,
                                                                      TimeSeries <T, double> forwardCurve, ICmdtyStorage <T> storage, Func <T, Day> settleDateRule,
                                                                      Func <Day, Day, double> discountFactors, Func <ICmdtyStorage <T>, IDoubleStateSpaceGridCalc> gridCalcFactory,
                                                                      IInterpolatorFactory interpolatorFactory, double numericalTolerance)
        {
            if (startingInventory < 0)
            {
                throw new ArgumentException("Inventory cannot be negative.", nameof(startingInventory));
            }

            if (currentPeriod.CompareTo(storage.EndPeriod) > 0)
            {
                return(new IntrinsicStorageValuationResults <T>(0.0, TimeSeries <T, StorageProfile> .Empty));
            }

            if (currentPeriod.Equals(storage.EndPeriod))
            {
                if (storage.MustBeEmptyAtEnd)
                {
                    if (startingInventory > 0) // TODO allow some tolerance for floating point numerical error?
                    {
                        throw new InventoryConstraintsCannotBeFulfilledException("Storage must be empty at end, but inventory is greater than zero.");
                    }
                    return(new IntrinsicStorageValuationResults <T>(0.0, TimeSeries <T, StorageProfile> .Empty));
                }

                double terminalMinInventory = storage.MinInventory(storage.EndPeriod);
                double terminalMaxInventory = storage.MaxInventory(storage.EndPeriod);

                if (startingInventory < terminalMinInventory)
                {
                    throw new InventoryConstraintsCannotBeFulfilledException("Current inventory is lower than the minimum allowed in the end period.");
                }

                if (startingInventory > terminalMaxInventory)
                {
                    throw new InventoryConstraintsCannotBeFulfilledException("Current inventory is greater than the maximum allowed in the end period.");
                }

                double cmdtyPrice = forwardCurve[storage.EndPeriod];
                double npv        = storage.TerminalStorageNpv(cmdtyPrice, startingInventory);
                return(new IntrinsicStorageValuationResults <T>(npv, TimeSeries <T, StorageProfile> .Empty));
            }

            TimeSeries <T, InventoryRange> inventorySpace = StorageHelper.CalculateInventorySpace(storage, startingInventory, currentPeriod);

            // TODO think of method to put in TimeSeries class to perform the validation check below in one line
            if (forwardCurve.IsEmpty)
            {
                throw new ArgumentException("Forward curve cannot be empty.", nameof(forwardCurve));
            }

            if (forwardCurve.Start.CompareTo(inventorySpace.Start) > 0)
            {
                throw new ArgumentException("Forward curve starts too late.", nameof(forwardCurve));
            }

            if (forwardCurve.End.CompareTo(inventorySpace.End) < 0)
            {
                throw new ArgumentException("Forward curve does not extend until storage end period.", nameof(forwardCurve));
            }

            // Calculate discount factor function
            Day dayToDiscountTo = currentPeriod.First <Day>(); // TODO IMPORTANT, this needs to change

            // Memoize the discount factor
            var discountFactorCache = new Dictionary <Day, double>(); // TODO do this in more elegant way and share with Tree calc

            double DiscountToCurrentDay(Day cashFlowDate)
            {
                if (!discountFactorCache.TryGetValue(cashFlowDate, out double discountFactor))
                {
                    discountFactor = discountFactors(dayToDiscountTo, cashFlowDate);
                    discountFactorCache[cashFlowDate] = discountFactor;
                }
                return(discountFactor);
            }

            // Perform backward induction
            var storageValueByInventory = new Func <double, double> [inventorySpace.Count];

            double cmdtyPriceAtEnd = forwardCurve[storage.EndPeriod];

            storageValueByInventory[inventorySpace.Count - 1] =
                finalInventory => storage.TerminalStorageNpv(cmdtyPriceAtEnd, finalInventory);

            int backCounter = inventorySpace.Count - 2;
            IDoubleStateSpaceGridCalc gridCalc = gridCalcFactory(storage);

            foreach (T periodLoop in inventorySpace.Indices.Reverse().Skip(1))
            {
                (double inventorySpaceMin, double inventorySpaceMax) = inventorySpace[periodLoop];
                double[] inventorySpaceGrid = gridCalc.GetGridPoints(inventorySpaceMin, inventorySpaceMax)
                                              .ToArray();
                var storageValuesGrid = new double[inventorySpaceGrid.Length];

                double cmdtyPrice = forwardCurve[periodLoop];
                Func <double, double> continuationValueByInventory = storageValueByInventory[backCounter + 1];

                Day    cmdtySettlementDate = settleDateRule(periodLoop);
                double discountFactorFromCmdtySettlement = DiscountToCurrentDay(cmdtySettlementDate);

                (double nextStepInventorySpaceMin, double nextStepInventorySpaceMax) = inventorySpace[periodLoop.Offset(1)];
                for (int i = 0; i < inventorySpaceGrid.Length; i++)
                {
                    double inventory = inventorySpaceGrid[i];
                    storageValuesGrid[i] = OptimalDecisionAndValue(storage, periodLoop, inventory, nextStepInventorySpaceMin,
                                                                   nextStepInventorySpaceMax, cmdtyPrice, continuationValueByInventory,
                                                                   discountFactorFromCmdtySettlement, DiscountToCurrentDay, numericalTolerance).StorageNpv;
                }

                storageValueByInventory[backCounter] =
                    interpolatorFactory.CreateInterpolator(inventorySpaceGrid, storageValuesGrid);
                backCounter--;
            }

            // Loop forward from start inventory choosing optimal decisions
            int numStorageProfiles = inventorySpace.Count + 1;
            var storageProfiles    = new StorageProfile[numStorageProfiles];
            var periods            = new T[numStorageProfiles];

            double inventoryLoop      = startingInventory;
            T      startActiveStorage = inventorySpace.Start.Offset(-1);

            for (int i = 0; i < numStorageProfiles; i++)
            {
                T              periodLoop = startActiveStorage.Offset(i);
                double         spotPrice  = forwardCurve[periodLoop];
                StorageProfile storageProfile;
                if (periodLoop.Equals(storage.EndPeriod))
                {
                    double endPeriodNpv = storage.MustBeEmptyAtEnd ? 0.0 : storage.TerminalStorageNpv(spotPrice, inventoryLoop);
                    storageProfile = new StorageProfile(inventoryLoop, 0.0, 0.0, 0.0, 0.0, endPeriodNpv);
                }
                else
                {
                    Day    cmdtySettlementDate = settleDateRule(periodLoop);
                    double discountFactorFromCmdtySettlement = DiscountToCurrentDay(cmdtySettlementDate);

                    Func <double, double> continuationValueByInventory = storageValueByInventory[i];
                    (double nextStepInventorySpaceMin, double nextStepInventorySpaceMax) = inventorySpace[periodLoop.Offset(1)];
                    (double _, double optimalInjectWithdraw, double cmdtyConsumedOnAction, double inventoryLoss, double optimalPeriodPv) =
                        OptimalDecisionAndValue(storage, periodLoop, inventoryLoop, nextStepInventorySpaceMin,
                                                nextStepInventorySpaceMax, spotPrice, continuationValueByInventory, discountFactorFromCmdtySettlement,
                                                DiscountToCurrentDay, numericalTolerance);

                    inventoryLoop += optimalInjectWithdraw - inventoryLoss;

                    double netVolume = -optimalInjectWithdraw - cmdtyConsumedOnAction;
                    storageProfile = new StorageProfile(inventoryLoop, optimalInjectWithdraw, cmdtyConsumedOnAction, inventoryLoss, netVolume, optimalPeriodPv);
                }
                storageProfiles[i] = storageProfile;
                periods[i]         = periodLoop;
            }

            double storageNpv = storageProfiles.Sum(profile => profile.PeriodPv);

            return(new IntrinsicStorageValuationResults <T>(storageNpv, new TimeSeries <T, StorageProfile>(periods, storageProfiles)));
        }