def intrinsic_value( cmdty_storage: CmdtyStorage, val_date: utils.TimePeriodSpecType, inventory: Union[float, int], forward_curve: pd.Series, interest_rates: pd.Series, settlement_rule: Callable[[pd.Period], date], num_inventory_grid_points: int = 100, numerical_tolerance: float = 1E-12) -> IntrinsicValuationResults: """ Calculates the intrinsic value of commodity storage. Args: settlement_rule (callable): Mapping function from pandas.Period type to the date on which the cmdty delivered in this period is settled. The pandas.Period parameter will have freq equal to the cmdty_storage parameter's freq property. """ if cmdty_storage.freq != forward_curve.index.freqstr: raise ValueError( "cmdty_storage and forward_curve have different frequencies.") time_period_type = utils.FREQ_TO_PERIOD_TYPE[cmdty_storage.freq] current_period = utils.from_datetime_like(val_date, time_period_type) net_forward_curve = utils.series_to_double_time_series( forward_curve, time_period_type) net_settlement_rule = utils.wrap_settle_for_dotnet(settlement_rule, cmdty_storage.freq) interest_rate_time_series = utils.series_to_double_time_series( interest_rates, utils.FREQ_TO_PERIOD_TYPE['D']) return net_intrinsic_calc(cmdty_storage, current_period, interest_rate_time_series, inventory, net_forward_curve, net_settlement_rule, num_inventory_grid_points, numerical_tolerance, time_period_type)
def __init__( self, freq: str, factors: tp.Iterable[tp.Tuple[float, utils.CurveType]], factor_corrs: FactorCorrsType, current_date: tp.Union[datetime, date, str, pd.Period], fwd_curve: utils.CurveType, sim_periods: tp.Iterable[tp.Union[pd.Period, datetime, date, str]], seed: tp.Optional[int] = None, antithetic: bool = False, # time_func: Callable[[Union[datetime, date], Union[datetime, date]], float] TODO add this back in ): factor_corrs = _validate_multi_factor_params(factors, factor_corrs) if freq not in utils.FREQ_TO_PERIOD_TYPE: raise ValueError( "freq parameter value of '{}' not supported. The allowable values can be found in the " "keys of the dict curves.FREQ_TO_PERIOD_TYPE.".format(freq)) time_period_type = utils.FREQ_TO_PERIOD_TYPE[freq] net_multi_factor_params = _create_net_multi_factor_params( factor_corrs, factors, time_period_type) net_forward_curve = utils.curve_to_net_dict(fwd_curve, time_period_type) net_current_date = utils.py_date_like_to_net_datetime(current_date) net_time_func = dotnet.Func[dotnet.DateTime, dotnet.DateTime, dotnet.Double]( net_sim.TimeFunctions.Act365) net_sim_periods = dotnet_cols_gen.List[time_period_type]() [ net_sim_periods.Add(utils.from_datetime_like(p, time_period_type)) for p in sim_periods ] if seed is None: mt_rand = net_sim.MersenneTwisterGenerator(antithetic) else: mt_rand = net_sim.MersenneTwisterGenerator(seed, antithetic) mt_rand = net_sim.IStandardNormalGeneratorWithSeed(mt_rand) self._net_simulator = net_sim.MultiFactor.MultiFactorSpotPriceSimulator[ time_period_type](net_multi_factor_params, net_current_date, net_forward_curve, net_sim_periods, net_time_func, mt_rand) self._sim_periods = [_to_pd_period(freq, p) for p in sim_periods] self._freq = freq
def three_factor_seasonal_value( cmdty_storage: CmdtyStorage, val_date: utils.TimePeriodSpecType, inventory: float, fwd_curve: pd.Series, interest_rates: pd.Series, settlement_rule: tp.Callable[[pd.Period], date], spot_mean_reversion: float, spot_vol: float, long_term_vol: float, seasonal_vol: float, num_sims: int, basis_funcs: str, discount_deltas: bool, seed: tp.Optional[int] = None, fwd_sim_seed: tp.Optional[int] = None, extra_decisions: tp.Optional[int] = None, num_inventory_grid_points: int = 100, numerical_tolerance: float = 1E-12, on_progress_update: tp.Optional[tp.Callable[[float], None]] = None, ) -> MultiFactorValuationResults: time_period_type = utils.FREQ_TO_PERIOD_TYPE[cmdty_storage.freq] net_current_period = utils.from_datetime_like(val_date, time_period_type) net_multi_factor_params = net_mf.MultiFactorParameters.For3FactorSeasonal[ time_period_type](spot_mean_reversion, spot_vol, long_term_vol, seasonal_vol, net_current_period, cmdty_storage.net_storage.EndPeriod) # Transform factors x_st -> x0, x_lt -> x1, x_sw -> x2 basis_func_transformed = basis_funcs.replace('x_st', 'x0').replace( 'x_lt', 'x1').replace('x_sw', 'x2') return _net_multi_factor_calc(cmdty_storage, fwd_curve, interest_rates, inventory, net_multi_factor_params, num_inventory_grid_points, num_sims, numerical_tolerance, on_progress_update, basis_func_transformed, seed, fwd_sim_seed, settlement_rule, time_period_type, val_date, discount_deltas, extra_decisions)
def trinomial_value(cmdty_storage: CmdtyStorage, val_date: utils.TimePeriodSpecType, inventory: float, forward_curve: pd.Series, spot_volatility: pd.Series, mean_reversion: float, time_step: float, interest_rates: pd.Series, settlement_rule: tp.Callable[[pd.Period], date], num_inventory_grid_points: int = 100, numerical_tolerance: float = 1E-12) -> float: """ Calculates the value of commodity storage using a one-factor trinomial tree. Args: settlement_rule (callable): Mapping function from pandas.Period type to the date on which the cmdty delivered in this period is settled. The pandas.Period parameter will have freq equal to the cmdty_storage parameter's freq property. """ if cmdty_storage.freq != forward_curve.index.freqstr: raise ValueError( "cmdty_storage and forward_curve have different frequencies.") if cmdty_storage.freq != spot_volatility.index.freqstr: raise ValueError( "cmdty_storage and spot_volatility have different frequencies.") time_period_type = utils.FREQ_TO_PERIOD_TYPE[cmdty_storage.freq] trinomial_calc = net_cs.TreeStorageValuation[time_period_type].ForStorage( cmdty_storage.net_storage) net_cs.ITreeAddStartingInventory[time_period_type]( trinomial_calc).WithStartingInventory(inventory) current_period = utils.from_datetime_like(val_date, time_period_type) net_cs.ITreeAddCurrentPeriod[time_period_type]( trinomial_calc).ForCurrentPeriod(current_period) net_forward_curve = utils.series_to_double_time_series( forward_curve, time_period_type) net_cs.ITreeAddForwardCurve[time_period_type]( trinomial_calc).WithForwardCurve(net_forward_curve) net_spot_volatility = utils.series_to_double_time_series( spot_volatility, time_period_type) net_cs.TreeStorageValuationExtensions.WithOneFactorTrinomialTree[ time_period_type](trinomial_calc, net_spot_volatility, mean_reversion, time_step) net_settlement_rule = utils.wrap_settle_for_dotnet(settlement_rule, cmdty_storage.freq) net_cs.ITreeAddCmdtySettlementRule[time_period_type]( trinomial_calc).WithCmdtySettlementRule(net_settlement_rule) interest_rate_time_series = utils.series_to_double_time_series( interest_rates, utils.FREQ_TO_PERIOD_TYPE['D']) net_cs.TreeStorageValuationExtensions.WithAct365ContinuouslyCompoundedInterestRateCurve[ time_period_type](trinomial_calc, interest_rate_time_series) net_cs.TreeStorageValuationExtensions.WithFixedNumberOfPointsOnGlobalInventoryRange[ time_period_type](trinomial_calc, num_inventory_grid_points) net_cs.TreeStorageValuationExtensions.WithLinearInventorySpaceInterpolation[ time_period_type](trinomial_calc) net_cs.ITreeAddNumericalTolerance[time_period_type]( trinomial_calc).WithNumericalTolerance(numerical_tolerance) npv = net_cs.ITreeCalculate[time_period_type](trinomial_calc).Calculate() return npv.NetPresentValue
def _net_multi_factor_calc(cmdty_storage, fwd_curve, interest_rates, inventory, net_multi_factor_params, num_inventory_grid_points, num_sims, numerical_tolerance, on_progress_update, basis_funcs, seed, fwd_sim_seed, settlement_rule, time_period_type, val_date, discount_deltas, extra_decisions): # Convert inputs to .NET types net_forward_curve = utils.series_to_double_time_series( fwd_curve, time_period_type) net_current_period = utils.from_datetime_like(val_date, time_period_type) net_grid_calc = net_cs.FixedSpacingStateSpaceGridCalc.CreateForFixedNumberOfPointsOnGlobalInventoryRange[ time_period_type](cmdty_storage.net_storage, num_inventory_grid_points) net_settlement_rule = utils.wrap_settle_for_dotnet(settlement_rule, cmdty_storage.freq) net_interest_rate_time_series = utils.series_to_double_time_series( interest_rates, utils.FREQ_TO_PERIOD_TYPE['D']) net_discount_func = net_cs.StorageHelper.CreateAct65ContCompDiscounterFromSeries( net_interest_rate_time_series) net_on_progress = utils.wrap_on_progress_for_dotnet(on_progress_update) logger.info( 'Compiling basis functions. Takes a few seconds on the first run.') net_basis_functions = net_cs.BasisFunctionsBuilder.Parse(basis_funcs) logger.info('Compilation of basis functions complete.') # Intrinsic calc logger.info('Calculating intrinsic value.') intrinsic_result = cs_intrinsic.net_intrinsic_calc( cmdty_storage, net_current_period, net_interest_rate_time_series, inventory, net_forward_curve, net_settlement_rule, num_inventory_grid_points, numerical_tolerance, time_period_type) logger.info('Calculation of intrinsic value complete.') # Multi-factor calc logger.info('Calculating LSMC value.') net_logger = utils.create_net_log_adapter(logger, net_cs.LsmcStorageValuation) lsmc = net_cs.LsmcStorageValuation(net_logger) net_lsmc_params_builder = net_cs.PythonHelpers.ObjectFactory.CreateLsmcValuationParamsBuilder[ time_period_type]() net_lsmc_params_builder.CurrentPeriod = net_current_period net_lsmc_params_builder.Inventory = inventory net_lsmc_params_builder.ForwardCurve = net_forward_curve net_lsmc_params_builder.Storage = cmdty_storage.net_storage net_lsmc_params_builder.SettleDateRule = net_settlement_rule net_lsmc_params_builder.DiscountFactors = net_discount_func net_lsmc_params_builder.GridCalc = net_grid_calc net_lsmc_params_builder.NumericalTolerance = numerical_tolerance net_lsmc_params_builder.BasisFunctions = net_basis_functions if net_on_progress is not None: net_lsmc_params_builder.OnProgressUpdate = net_on_progress net_lsmc_params_builder.DiscountDeltas = discount_deltas if extra_decisions is not None: net_lsmc_params_builder.ExtraDecisions = extra_decisions net_lsmc_params_builder.SimulateWithMultiFactorModelAndMersenneTwister( net_multi_factor_params, num_sims, seed, fwd_sim_seed) net_lsmc_params = net_lsmc_params_builder.Build() net_val_results = lsmc.Calculate[time_period_type](net_lsmc_params) logger.info('Calculation of LSMC value complete.') deltas = utils.net_time_series_to_pandas_series(net_val_results.Deltas, cmdty_storage.freq) expected_profile = cs_intrinsic.profile_to_data_frame( cmdty_storage.freq, net_val_results.ExpectedStorageProfile) trigger_prices = _trigger_prices_to_data_frame( cmdty_storage.freq, net_val_results.TriggerPrices) trigger_profiles = _trigger_profiles_to_data_frame( cmdty_storage.freq, net_val_results.TriggerPriceVolumeProfiles) sim_spot_regress = utils.net_panel_to_data_frame( net_val_results.RegressionSpotPriceSim, cmdty_storage.freq) sim_spot_valuation = utils.net_panel_to_data_frame( net_val_results.ValuationSpotPriceSim, cmdty_storage.freq) sim_inventory = utils.net_panel_to_data_frame( net_val_results.InventoryBySim, cmdty_storage.freq) sim_inject_withdraw = utils.net_panel_to_data_frame( net_val_results.InjectWithdrawVolumeBySim, cmdty_storage.freq) sim_cmdty_consumed = utils.net_panel_to_data_frame( net_val_results.CmdtyConsumedBySim, cmdty_storage.freq) sim_inventory_loss = utils.net_panel_to_data_frame( net_val_results.InventoryLossBySim, cmdty_storage.freq) sim_net_volume = utils.net_panel_to_data_frame( net_val_results.NetVolumeBySim, cmdty_storage.freq) sim_pv = utils.net_panel_to_data_frame(net_val_results.PvByPeriodAndSim, cmdty_storage.freq) return MultiFactorValuationResults( net_val_results.Npv, deltas, expected_profile, intrinsic_result.npv, intrinsic_result.profile, sim_spot_regress, sim_spot_valuation, sim_inventory, sim_inject_withdraw, sim_cmdty_consumed, sim_inventory_loss, sim_net_volume, sim_pv, trigger_prices, trigger_profiles)
def __init__(self, freq: str, storage_start: utils.TimePeriodSpecType, storage_end: utils.TimePeriodSpecType, injection_cost: Union[float, pd.Series], withdrawal_cost: Union[float, pd.Series], ratchets: RatchetsType = None, ratchet_interp: Optional[RatchetInterp] = None, min_inventory: Union[None, float, int, pd.Series] = None, max_inventory: Union[None, float, int, pd.Series] = None, max_injection_rate: Union[None, float, int, pd.Series] = None, max_withdrawal_rate: Union[None, float, int, pd.Series] = None, cmdty_consumed_inject: Union[None, float, int, pd.Series] = None, cmdty_consumed_withdraw: Union[None, float, int, pd.Series] = None, terminal_storage_npv: Union[None, Callable[[float, float], float]] = None, inventory_loss: Union[None, float, int, pd.Series] = None, inventory_cost: Union[None, float, int, pd.Series] = None): if freq not in utils.FREQ_TO_PERIOD_TYPE: raise ValueError( "freq parameter value of '{}' not supported. The allowable values can be found in the keys of the dict curves.FREQ_TO_PERIOD_TYPE." .format(freq)) time_period_type = utils.FREQ_TO_PERIOD_TYPE[freq] start_period = utils.from_datetime_like(storage_start, time_period_type) end_period = utils.from_datetime_like(storage_end, time_period_type) builder = net_cs.IBuilder[time_period_type]( net_cs.CmdtyStorage[time_period_type].Builder) builder = builder.WithActiveTimePeriod(start_period, end_period) net_constraints = dotnet_cols_gen.List[ net_cs.InjectWithdrawRangeByInventoryAndPeriod[time_period_type]]() if ratchets is not None: utils.raise_if_not_none( min_inventory, "min_inventory parameter should not be provided if ratchets parameter is provided." ) utils.raise_if_not_none( max_inventory, "max_inventory parameter should not be provided if ratchets parameter is provided." ) utils.raise_if_not_none( max_injection_rate, "max_injection_rate parameter should not be provided if ratchets parameter is provided." ) utils.raise_if_not_none( max_withdrawal_rate, "max_withdrawal_rate parameter should not be provided if ratchets parameter is provided." ) utils.raise_if_none( ratchet_interp, "ratchet_interp parameter should be provided if ratchets parameter is provided." ) for period, rates_by_inventory in ratchets: net_period = utils.from_datetime_like(period, time_period_type) net_rates_by_inventory = dotnet_cols_gen.List[ net_cs.InjectWithdrawRangeByInventory]() for inventory, min_rate, max_rate in rates_by_inventory: net_rates_by_inventory.Add( net_cs.InjectWithdrawRangeByInventory( inventory, net_cs.InjectWithdrawRange(min_rate, max_rate))) net_constraints.Add( net_cs. InjectWithdrawRangeByInventoryAndPeriod[time_period_type]( net_period, net_rates_by_inventory)) builder = net_cs.IAddInjectWithdrawConstraints[time_period_type]( builder) if ratchet_interp == RatchetInterp.LINEAR: net_cs.CmdtyStorageBuilderExtensions.WithTimeAndInventoryVaryingInjectWithdrawRatesPiecewiseLinear[ time_period_type](builder, net_constraints) elif ratchet_interp == RatchetInterp.STEP: net_cs.CmdtyStorageBuilderExtensions.WithStepRatchets[ time_period_type](builder, net_constraints) if terminal_storage_npv is None: logger.warning( 'When ratchet_interp is RatchetInterp.STEP it is advisable to specify ' 'terminal_storage_npv otherwise exceptions are likely to occur during valuation.' ) else: utils.raise_if_not_none( ratchet_interp, "ratchet_interp should not be provided if ratchets parameter is not provided." ) utils.raise_if_none( min_inventory, "min_inventory parameter should be provided if ratchets parameter is not provided." ) utils.raise_if_none( max_inventory, "max_inventory parameter should be provided if ratchets parameter is not provided." ) utils.raise_if_none( max_injection_rate, "max_injection_rate parameter should be provided if ratchets parameter is not provided." ) utils.raise_if_none( max_withdrawal_rate, "max_withdrawal_rate parameter should be provided if ratchets parameter is not provided." ) builder = net_cs.IAddInjectWithdrawConstraints[time_period_type]( builder) max_injection_rate_is_scalar = utils.is_scalar(max_injection_rate) max_withdrawal_rate_is_scalar = utils.is_scalar( max_withdrawal_rate) if max_injection_rate_is_scalar and max_withdrawal_rate_is_scalar: net_cs.CmdtyStorageBuilderExtensions.WithConstantInjectWithdrawRange[ time_period_type](builder, -max_withdrawal_rate, max_injection_rate) else: if max_injection_rate_is_scalar: max_injection_rate = pd.Series( data=[max_injection_rate] * len(max_withdrawal_rate), index=max_withdrawal_rate.index) elif max_withdrawal_rate_is_scalar: max_withdrawal_rate = pd.Series( data=[max_withdrawal_rate] * len(max_injection_rate), index=max_injection_rate.index) inject_withdraw_series = max_injection_rate.combine( max_withdrawal_rate, lambda inj_rate, with_rate: (-with_rate, inj_rate)).dropna() net_inj_with_series = utils.series_to_time_series( inject_withdraw_series, time_period_type, net_cs.InjectWithdrawRange, lambda tup: net_cs.InjectWithdrawRange(tup[0], tup[1])) builder.WithInjectWithdrawRangeSeries(net_inj_with_series) builder = net_cs.IAddMinInventory[time_period_type](builder) if isinstance(min_inventory, pd.Series): net_series_min_inventory = utils.series_to_double_time_series( min_inventory, time_period_type) builder.WithMinInventoryTimeSeries(net_series_min_inventory) else: # Assume min_inventory is a constaint number builder.WithConstantMinInventory(min_inventory) builder = net_cs.IAddMaxInventory[time_period_type](builder) if isinstance(max_inventory, pd.Series): net_series_max_inventory = utils.series_to_double_time_series( max_inventory, time_period_type) builder.WithMaxInventoryTimeSeries(net_series_max_inventory) else: # Assume max_inventory is a constaint number builder.WithConstantMaxInventory(max_inventory) builder = net_cs.IAddInjectionCost[time_period_type](builder) if utils.is_scalar(injection_cost): builder.WithPerUnitInjectionCost(injection_cost) else: net_series_injection_cost = utils.series_to_double_time_series( injection_cost, time_period_type) builder.WithPerUnitInjectionCostTimeSeries( net_series_injection_cost) builder = net_cs.IAddCmdtyConsumedOnInject[time_period_type](builder) if cmdty_consumed_inject is not None: if utils.is_scalar(cmdty_consumed_inject): builder.WithFixedPercentCmdtyConsumedOnInject( cmdty_consumed_inject) else: net_series_cmdty_consumed_inject = utils.series_to_double_time_series( cmdty_consumed_inject, time_period_type) builder.WithPercentCmdtyConsumedOnInjectTimeSeries( net_series_cmdty_consumed_inject) else: builder.WithNoCmdtyConsumedOnInject() builder = net_cs.IAddWithdrawalCost[time_period_type](builder) if utils.is_scalar(withdrawal_cost): builder.WithPerUnitWithdrawalCost(withdrawal_cost) else: net_series_withdrawal_cost = utils.series_to_double_time_series( withdrawal_cost, time_period_type) builder.WithPerUnitWithdrawalCostTimeSeries( net_series_withdrawal_cost) builder = net_cs.IAddCmdtyConsumedOnWithdraw[time_period_type](builder) if cmdty_consumed_withdraw is not None: if utils.is_scalar(cmdty_consumed_withdraw): builder.WithFixedPercentCmdtyConsumedOnWithdraw( cmdty_consumed_withdraw) else: net_series_cmdty_consumed_withdraw = utils.series_to_double_time_series( cmdty_consumed_withdraw, time_period_type) builder.WithPercentCmdtyConsumedOnWithdrawTimeSeries( net_series_cmdty_consumed_withdraw) else: builder.WithNoCmdtyConsumedOnWithdraw() builder = net_cs.IAddCmdtyInventoryLoss[time_period_type](builder) if inventory_loss is not None: if utils.is_scalar(inventory_loss): builder.WithFixedPercentCmdtyInventoryLoss(inventory_loss) else: net_series_inventory_loss = utils.series_to_double_time_series( inventory_loss, time_period_type) builder.WithCmdtyInventoryLossTimeSeries( net_series_inventory_loss) else: builder.WithNoCmdtyInventoryLoss() builder = net_cs.IAddCmdtyInventoryCost[time_period_type](builder) if inventory_cost is not None: if utils.is_scalar(inventory_cost): builder.WithFixedPerUnitInventoryCost(inventory_cost) else: net_series_inventory_cost = utils.series_to_double_time_series( inventory_cost, time_period_type) builder.WithPerUnitInventoryCostTimeSeries( net_series_inventory_cost) else: builder.WithNoInventoryCost() builder = net_cs.IAddTerminalStorageState[time_period_type](builder) if terminal_storage_npv is None: builder.MustBeEmptyAtEnd() else: builder.WithTerminalInventoryNpv( dotnet.Func[dotnet.Double, dotnet.Double, dotnet.Double](terminal_storage_npv)) self._net_storage = net_cs.IBuildCmdtyStorage[time_period_type]( builder).Build() self._freq = freq
def _net_time_period(self, period): time_period_type = utils.FREQ_TO_PERIOD_TYPE[self._freq] return utils.from_datetime_like(period, time_period_type)