.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "api/gallery/portfolio/mean_variance/plot_04_one_over_n.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code or to run this example in your browser via JupyterLite. .. rst-class:: sphx-glr-example-title .. _sphx_glr_api_gallery_portfolio_mean_variance_plot_04_one_over_n.py: DeMiguel-Garlappi-Uppal: optimal versus 1/N =========================================== DeMiguel, Garlappi and Uppal (2009) re-estimated fourteen optimized portfolio rules on rolling windows and compared their out-of-sample Sharpe ratios with simply holding every asset equally. None beat 1/N reliably. Here the true model is known: ten assets with a genuinely better tangency portfolio. The sample mean-variance rule, the tangency portfolio of the estimated moments, still loses to 1/N unless its estimation window is very long, because errors in the estimated means swamp the gain from optimizing. .. GENERATED FROM PYTHON SOURCE LINES 16-46 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np from financialkit.portfolio import equal_weight, min_variance_portfolio, out_of_sample_returns, tangency_portfolio, two_fund_allocation from financialkit.portfolio.visualizers import plot_out_of_sample rng = np.random.default_rng(4) d = 10 mu = np.linspace(0.006, 0.010, d) vol = np.linspace(0.03, 0.08, d) corr = 0.4 + 0.6 * np.eye(d) cov = corr * np.outer(vol, vol) true_t = tangency_portfolio(mu, cov) ew = equal_weight(d) print(f"true monthly Sharpe: tangency {true_t.sharpe_ratio():.3f}, 1/N {ew @ mu / np.sqrt(ew @ cov @ ew):.3f}") R = rng.multivariate_normal(mu, cov, size=600) def sample_mean_variance(past): # Sigma^-1 mu scaled to unit gross exposure: the sample tangency direction, # well defined even when the estimated minimum-variance mean is negative. w = two_fund_allocation(past.mean(axis=0), np.cov(past, rowvar=False), 0.0, 1.0).weights return w / np.abs(w).sum() def sample_min_variance(past): return min_variance_portfolio(np.cov(past, rowvar=False), long_only=True).weights .. rst-class:: sphx-glr-script-out .. code-block:: none true monthly Sharpe: tangency 0.243, 1/N 0.213 .. GENERATED FROM PYTHON SOURCE LINES 47-49 Out of sample with a 60-month window ------------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 49-59 .. code-block:: Python results = [ out_of_sample_returns(R, lambda past: ew, 60, "1/N"), out_of_sample_returns(R, sample_mean_variance, 60, "sample mean-variance"), out_of_sample_returns(R, sample_min_variance, 60, "long-only min variance"), ] plot_out_of_sample(results) for res in results: print(f"{res.name:>24}: Sharpe {res.sharpe:.3f}, turnover {res.turnover:.2f}") .. image-sg:: /api/gallery/portfolio/mean_variance/images/sphx_glr_plot_04_one_over_n_001.png :alt: plot 04 one over n :srcset: /api/gallery/portfolio/mean_variance/images/sphx_glr_plot_04_one_over_n_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none 1/N: Sharpe 0.163, turnover 0.00 sample mean-variance: Sharpe 0.054, turnover 0.20 long-only min variance: Sharpe 0.176, turnover 0.06 .. GENERATED FROM PYTHON SOURCE LINES 60-66 How long a window does optimization need? ----------------------------------------- The true Sharpe ratio of the sample mean-variance rule, averaged over histories, approaches the optimum only slowly as the window grows. Each point evaluates the estimated weights under the true moments. .. GENERATED FROM PYTHON SOURCE LINES 66-85 .. code-block:: Python windows = [24, 60, 120, 240, 480, 960] sr_est = [] for T in windows: vals = [] for _ in range(40): X = rng.multivariate_normal(mu, cov, size=T) w = sample_mean_variance(X) vals.append(w @ mu / np.sqrt(w @ cov @ w)) sr_est.append(np.mean(vals)) fig, ax = plt.subplots(figsize=(7, 3.5)) ax.semilogx(windows, sr_est, "o-", label="sample mean-variance") ax.axhline(true_t.sharpe_ratio(), color="C2", ls="--", label="true tangency") ax.axhline(ew @ mu / np.sqrt(ew @ cov @ ew), color="C3", label="1/N") ax.set_xlabel("estimation window (months)") ax.set_ylabel("true monthly Sharpe ratio") ax.legend() fig.tight_layout() plt.show() .. image-sg:: /api/gallery/portfolio/mean_variance/images/sphx_glr_plot_04_one_over_n_002.png :alt: plot 04 one over n :srcset: /api/gallery/portfolio/mean_variance/images/sphx_glr_plot_04_one_over_n_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.581 seconds) .. _sphx_glr_download_api_gallery_portfolio_mean_variance_plot_04_one_over_n.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../../../lite/lab/index.html?path=api/gallery/portfolio/mean_variance/plot_04_one_over_n.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_04_one_over_n.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_04_one_over_n.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_04_one_over_n.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_