Skip to content

Commit 67e0844

Browse files
committed
feat: use fixed percentage strategy in Portfolio.dcf.find_the_largest_withdrawals_size
1 parent 72358a5 commit 67e0844

File tree

3 files changed

+118
-35
lines changed

3 files changed

+118
-35
lines changed

main.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@
1414
assets=["MCFTR.INDX", "RUCBTRNS.INDX"],
1515
weights=[.3, .7],
1616
inflation=True,
17-
# first_date="2014-01",
1817
ccy="RUB",
1918
rebalancing_period="year",
2019
)
2120

2221
# Fixed Percentage strategy
2322
pc = ok.PercentageStrategy(pf)
23+
pc.initial_investment = 10_000
2424
pc.frequency = "year"
2525
pc.percentage = -0.08
2626

2727
# Fixed Amount strategy
2828
ind = ok.IndexationStrategy(pf)
29-
ind.initial_investment = 100
29+
ind.initial_investment = 10_000
3030
ind.frequency = "year"
31-
ind.amount = -0.5 * 12
31+
ind.amount = -1_000
3232
ind.indexation = "inflation"
3333

3434
# TimeSeries strategy
@@ -38,11 +38,11 @@
3838
}
3939

4040
ts = ok.TimeSeriesStrategy(pf)
41-
ts.initial_investment = 1_000
41+
ts.initial_investment = 10_000
4242
ts.time_series_dic = d
4343

4444
# Assign a strategy
45-
pf.dcf.cashflow_parameters = ind
45+
pf.dcf.cashflow_parameters = pc
4646
pf.dcf.discount_rate = 0.10
4747
pf.dcf.use_discounted_values = False
4848

@@ -51,24 +51,26 @@
5151
# Set Monte Carlo
5252
pf.dcf.set_mc_parameters(
5353
distribution="t",
54-
period=10,
54+
period=50,
5555
number=100
5656
)
5757

58-
# w = pf.dcf.find_the_largest_withdrawals_size(
59-
# min_amount=-100_000,
60-
# max_amount=-5_000,
61-
# withdrawal_steps=10,
62-
# confidence_level=0.50,
63-
# goal="survival_period", # survival_period maintain_balance
64-
# target_survival_period=25
65-
# )
66-
# print(w)
58+
largest_withdrawal = pf.dcf.find_the_largest_withdrawals_size(
59+
withdrawal_steps=30,
60+
confidence_level=0.50,
61+
goal="survival_period",
62+
threshold=0.10,
63+
target_survival_period=25
64+
)
65+
print(largest_withdrawal)
66+
67+
# print(pf.dcf.monte_carlo_survival_period(threshold=0.05).describe())
6768

6869

69-
df = pf.dcf.monte_carlo_wealth_pv
7070

71-
df.plot()
71+
# df = pf.dcf.monte_carlo_wealth_pv
72+
73+
# df.plot()
7274
# print("portfolio balance \n", df.iloc[-1, :].describe())
7375

7476
# pf.dcf.plot_forecast_monte_carlo(backtest=True)
@@ -86,7 +88,8 @@
8688
# print(s.iloc[-1].quantile(50/100))
8789

8890
# plt.figure(figsize=(20, 12))
89-
plt.yscale("log") # log or linear
91+
# plt.yscale("linear") # log or linear
92+
# plt.legend("")
9093
# df.plot()
9194
# plt.savefig('time_series.png')
92-
plt.show()
95+
# plt.show()

okama/portfolio.py

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2812,6 +2812,7 @@ def monte_carlo_survival_period(
28122812
threshold: float
28132813
) -> pd.Series:
28142814
"""
2815+
# TODO: update docs
28152816
Generate a survival period distribution for a portfolio with cash flows by Monte Carlo simulation.
28162817
28172818
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(
28652866

28662867
def find_the_largest_withdrawals_size(
28672868
self,
2868-
min_amount: float,
2869-
max_amount: float,
28702869
withdrawal_steps: int,
28712870
confidence_level: float,
28722871
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+
"""
28772944
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)
28822963
if goal == "maintain_balance":
28832964
wealth_at_quantile = self.monte_carlo_wealth.iloc[-1, :].quantile(confidence_level)
2884-
# print(f"{wealth_at_quantile=}")
28852965
condition = (sp_at_quantile == self.mc.period) and (wealth_at_quantile >= self.initial_investment_fv)
28862966
if condition:
2887-
max_withdrawal = a
2967+
max_withdrawal = size
28882968
break
28892969
elif goal == "survival_period":
28902970
condition = sp_at_quantile >= target_survival_period
28912971
if condition:
2892-
max_withdrawal = a
2972+
max_withdrawal = size
28932973
break
28942974

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()
28972978
return max_withdrawal
28982979

28992980

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ scipy = ">=1.9.0"
3434
matplotlib = ">=3.5.1"
3535
requests = "*"
3636
joblib = "*"
37-
setuptools = "*"
3837

3938
[tool.poetry.group.test]
4039
optional = false

0 commit comments

Comments
 (0)