@@ -2812,6 +2812,7 @@ def monte_carlo_survival_period(
2812
2812
threshold : float
2813
2813
) -> pd .Series :
2814
2814
"""
2815
+ # TODO: update docs
2815
2816
Generate a survival period distribution for a portfolio with cash flows by Monte Carlo simulation.
2816
2817
2817
2818
It's possible to analyze the distribution finding "min", "max" and percentiles to see for how long will last
@@ -2865,35 +2866,115 @@ def monte_carlo_survival_period(
2865
2866
2866
2867
def find_the_largest_withdrawals_size (
2867
2868
self ,
2868
- min_amount : float ,
2869
- max_amount : float ,
2870
2869
withdrawal_steps : int ,
2871
2870
confidence_level : float ,
2872
2871
goal : str ,
2873
- target_survival_period : int = 25
2874
- ):
2875
- amount_range = np .linspace (min_amount , max_amount , withdrawal_steps )
2876
- saved_amount = self .cashflow_parameters .amount
2872
+ threshold : float ,
2873
+ target_survival_period : int
2874
+ ) -> float :
2875
+ """
2876
+ Find the largest withdrawals size for Monte Carlo simulation according to Cashflow Strategy.
2877
+
2878
+ It's possible to find the largest withdrawl with 2 kind of goals:
2879
+ — 'maintain_balance' to keep the purchasing power of the invesments after inflation
2880
+ for the whole period defined in Monte Carlo parameteres.
2881
+ — 'survival_period' to keep positive balance for a period defined by 'target_survival_period'.
2882
+ The method works with IndexationStrategy and PercentageStrategy only.
2883
+
2884
+ The withdrawal size defined in cash flow strategy must be negative.
2885
+
2886
+ Returns
2887
+ -------
2888
+ float
2889
+ the largest withdrawals size according to Cashflow Strategy.
2890
+
2891
+ Parameters
2892
+ ----------
2893
+ withdrawal_steps : int
2894
+ Number of intermediate values for the withdrawal size.
2895
+ The withdrawal size varies from 100% of the initial investment to zero.
2896
+
2897
+ confidence_level : float
2898
+ Confidence level in percents (form 0 to 100).
2899
+ Confidence level defines the percentile of Monte Carlo time series. 0.01 or 0.05 are the examples of "bad"
2900
+ scenarios. 0.50 is mediane (50% percentile). 0.95 or 0.99 are optimiststic scenarios.
2901
+
2902
+ goal : {'maintain_balance', 'survival_period'}
2903
+ 'maintain_balance' - the goal is to keep the purchasing power of the invesments after inflation
2904
+ for the whole period defined in Monte Carlo parameteres.
2905
+ 'survival_period' - the goal is to keep positive balance
2906
+ for a period defined by 'target_survival_period'.
2907
+
2908
+ threshold : float
2909
+ The percentage of inititial investments when the portfolio balance is considered voided.
2910
+ Important for the "fixed_percentage" Cash flow strategy.
2911
+
2912
+ target_survival_period: int
2913
+ The smallest acceptable survival period. It wokrs with the 'survival_period' goal only.
2914
+
2915
+ Examples
2916
+ --------
2917
+ >>> pf = ok.Portfolio(
2918
+ assets=["MCFTR.INDX", "RUCBTRNS.INDX"],
2919
+ weights=[.3, .7],
2920
+ inflation=True,
2921
+ ccy="RUB",
2922
+ rebalancing_period="year",
2923
+ )
2924
+ >>> # Fixed Percentage strategy
2925
+ >>> pc = ok.PercentageStrategy(pf)
2926
+ >>> pc.initial_investment = 10_000
2927
+ >>> pc.frequency = "year"
2928
+ >>> # Assign a strategy
2929
+ >>> pf.dcf.cashflow_parameters = pc
2930
+ >>> # Set Monte Carlo parameters
2931
+ >>> pf.dcf.set_mc_parameters(
2932
+ distribution="t",
2933
+ period=50,
2934
+ number=100
2935
+ )
2936
+ >>> pf.dcf.find_the_largest_withdrawals_size(
2937
+ withdrawal_steps=30,
2938
+ confidence_level=0.50,
2939
+ goal="survival_period",
2940
+ threshold=0.10,
2941
+ target_survival_period=25
2942
+ )
2943
+ """
2877
2944
max_withdrawal = 0
2878
- for a in amount_range :
2879
- # print("testing:", a)
2880
- self .cashflow_parameters .amount = a
2881
- sp_at_quantile = self .monte_carlo_survival_period .quantile (confidence_level )
2945
+ if confidence_level > 1 or confidence_level < 0 :
2946
+ raise ValueError ("confidence level must be between 0 and 1" )
2947
+ if threshold > 1 or threshold < 0 :
2948
+ raise ValueError ("threshold must be between 0 and 1" )
2949
+ if self .cashflow_parameters .name == "fixed_amount" :
2950
+ saved_amount = self .cashflow_parameters .amount
2951
+ size_range = np .linspace (- self .cashflow_parameters .initial_investment , 0 , withdrawal_steps )
2952
+ elif self .cashflow_parameters .name == "fixed_percentage" :
2953
+ size_range = np .linspace (- 1 , 0 , withdrawal_steps )
2954
+ else :
2955
+ raise TypeError ("find_the_largest_withdrawals_size works with 'fixed_amount' or 'fixed_percentag' only." )
2956
+ for size in size_range :
2957
+ if self .cashflow_parameters .name == "fixed_amount" :
2958
+ self .cashflow_parameters .amount = size
2959
+ elif self .cashflow_parameters .name == "fixed_percentage" :
2960
+ self .cashflow_parameters .percentage = size
2961
+
2962
+ sp_at_quantile = self .monte_carlo_survival_period (threshold = threshold ).quantile (confidence_level )
2882
2963
if goal == "maintain_balance" :
2883
2964
wealth_at_quantile = self .monte_carlo_wealth .iloc [- 1 , :].quantile (confidence_level )
2884
- # print(f"{wealth_at_quantile=}")
2885
2965
condition = (sp_at_quantile == self .mc .period ) and (wealth_at_quantile >= self .initial_investment_fv )
2886
2966
if condition :
2887
- max_withdrawal = a
2967
+ max_withdrawal = size
2888
2968
break
2889
2969
elif goal == "survival_period" :
2890
2970
condition = sp_at_quantile >= target_survival_period
2891
2971
if condition :
2892
- max_withdrawal = a
2972
+ max_withdrawal = size
2893
2973
break
2894
2974
2895
- self .cashflow_parameters .amount = saved_amount
2896
- # print(f"{max_withdrawal=}")
2975
+ if self .cashflow_parameters .name == "fixed_amount" :
2976
+ self .cashflow_parameters .amount = saved_amount
2977
+ self .cashflow_parameters ._clear_cf_cache ()
2897
2978
return max_withdrawal
2898
2979
2899
2980
0 commit comments