From 531a19abf1d9eeebc35978dcf248a533ec55abb6 Mon Sep 17 00:00:00 2001 From: Sergey Kikevich Date: Sun, 30 Jan 2022 18:00:02 +0300 Subject: [PATCH] feat: add mdp_points property to EfficientFrontier mdp_points is a DataFrame with the Most diversified portfolios points. --- main.py | 5 ++- okama/frontier/single_period.py | 72 +++++++++++++++++++++++++++++++++ tests/test_frontier.py | 7 ++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index e494d76..ed1cfa8 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ import okama as ok -pf = ok.Portfolio(['SPY.US', 'AGG.US'], weights=[.7, .3]) +ls3 = ['MCFTR.INDX', 'RGBITR.INDX', 'GC.COMM'] +y = ok.EfficientFrontier(assets=ls3, ccy='USD', n_points=10) -print(pf.diversification_ratio) +print(y.mdp_points) diff --git a/okama/frontier/single_period.py b/okama/frontier/single_period.py index b7f311d..0611d29 100644 --- a/okama/frontier/single_period.py +++ b/okama/frontier/single_period.py @@ -93,6 +93,7 @@ def __init__( self.n_points = n_points self.labels_are_tickers = ticker_names self._ef_points = pd.DataFrame(dtype=float) + self._mdp_points = pd.DataFrame(dtype=float) def __repr__(self): dic = { @@ -707,6 +708,77 @@ def ef_points(self) -> pd.DataFrame: self._ef_points = df return self._ef_points + @property + def mdp_points(self) -> pd.DataFrame: + """ + Generate Most diversified portfolios line. + + Each point on the Most diversified portfolios line is a portfolio with optimized + Diversification ratio for a given return. + + The points are obtained through the constrained optimization process (optimization with bounds). + Bounds are defined with 'bounds' property. + + Returns + ------- + DataFrame + Table of weights and risk/return values for the Efficient Frontier. + The columns: + + - assets weights + - CAGR (geometric mean) + - Mean return (arithmetic mean) + - Risk (standard deviation) + - Diversification ratio + + All the values are annualized. + + Examples + -------- + >>> ls4 = ['SP500TR.INDX', 'MCFTR.INDX', 'RGBITR.INDX', 'GC.COMM'] + >>> y = ok.EfficientFrontier(assets=ls4, ccy='RUB', last_date='2021-12', n_points=100) + >>> y.mdp_points # print mdp weights, risk, mean return, CAGR and Diversification ratio + Risk Mean return CAGR ... MCFTR.INDX RGBITR.INDX SP500TR.INDX + 0 0.066040 0.094216 0.092220 ... 2.081668e-16 1.000000e+00 0.000000e+00 + 1 0.064299 0.095342 0.093451 ... 0.000000e+00 9.844942e-01 5.828671e-16 + 2 0.062761 0.096468 0.094670 ... 0.000000e+00 9.689885e-01 1.110223e-16 + 3 0.061445 0.097595 0.095874 ... 5.828671e-16 9.534827e-01 0.000000e+00 + 4 0.060364 0.098724 0.097065 ... 3.191891e-16 9.379769e-01 0.000000e+00 + .. ... ... ... ... ... ... ... + 95 0.258857 0.205984 0.178346 ... 8.840844e-01 1.387779e-17 0.000000e+00 + 96 0.266583 0.207214 0.177941 ... 9.130633e-01 3.469447e-18 0.000000e+00 + 97 0.274594 0.208446 0.177432 ... 9.420422e-01 0.000000e+00 1.075529e-16 + 98 0.282873 0.209678 0.176820 ... 9.710211e-01 2.428613e-17 6.938894e-18 + 99 0.291402 0.210912 0.176103 ... 1.000000e+00 2.775558e-16 3.951094e-09 + [100 rows x 8 columns] + + To plot the Most diversification portfolios line use the DataFrame with the points data. + Additionaly 'Plot.plot_assets()' can be used to show the assets in the chart. + + >>> import matplotlib.pyplot as plt + >>> fig = plt.figure() + >>> # Plot the assets points + >>> y.plot_assets(kind='cagr') # kind should be set to "cagr" as we take "CAGR" column from the ef_points. + >>> ax = plt.gca() + >>> # Plot the Most diversified portfolios line + >>> df = y.mdp_points + >>> ax.plot(df['Risk'], df['CAGR']) # we chose to plot CAGR which is geometric mean of return series + >>> # Set the axis labels and the title + >>> ax.set_title('Most diversified portfolios line') + >>> ax.set_xlabel('Risk (Standard Deviation)') + >>> ax.set_ylabel('Return (CAGR)') + >>> plt.show() + """ + if self._mdp_points.empty: + target_rs = self.mean_return_range + df = pd.DataFrame(dtype="float") + for x in target_rs: + row = self.get_most_diversified_portfolio(target_return=x, monthly_return=True) + df = df.append(row, ignore_index=True) + df = Frame.change_columns_order(df, ["Risk", "Mean return", "CAGR"]) + self._mdp_points = df + return self._mdp_points + def get_monte_carlo(self, n: int = 100, kind: str = "mean") -> pd.DataFrame: """ Generate N random portfolios with Monte Carlo simulation. diff --git a/tests/test_frontier.py b/tests/test_frontier.py index b893725..056d76d 100644 --- a/tests/test_frontier.py +++ b/tests/test_frontier.py @@ -126,6 +126,12 @@ def test_get_most_diversified_portfolio(init_efficient_frontier): assert_series_equal(df, df_expected, rtol=1e-03) +@mark.frontier +def test_mdp_points(init_efficient_frontier_three_assets): + assert init_efficient_frontier_three_assets.mdp_points['Mean return'].iloc[10] == approx(0.12039, rel=1e-2) + assert init_efficient_frontier_three_assets.mdp_points['Diversification ratio'].iloc[10] == approx(1.6050, rel=1e-2) + + @mark.frontier def test_plot_cml(init_efficient_frontier): rf_rate = 0.02 @@ -133,6 +139,7 @@ def test_plot_cml(init_efficient_frontier): expected = np.array([[0, 0.11053], [0.02, 0.1578]]) assert_allclose(axes_data, expected, atol=1e-2) + @mark.frontier def test_plot_transition_map(init_efficient_frontier_three_assets): axes_data = np.array(init_efficient_frontier_three_assets.plot_transition_map(cagr=False).lines[0].get_data())