Mean-Variance Portfolio Class ============================= Without doubt, the Markowitz (1952) **mean-variance portfolio theory** is a cornerstone of modern financial theory. This section illustrates the use of the ``mean_variance_portfolio`` class to implement this approach. .. code:: python from dx import * import seaborn as sns; sns.set() Market Environment and Portfolio Object --------------------------------------- We start by instantiating a ``market environment`` object which in particular contains a list of **ticker symbols** in which we are interested in. .. code:: python ma = market_environment('ma', dt.date(2010, 1, 1)) ma.add_list('symbols', ['AAPL', 'GOOG', 'MSFT', 'FB']) ma.add_constant('source', 'google') ma.add_constant('final date', dt.date(2014, 3, 1)) Using pandas under the hood, the class **retrieves historial stock price data** from either Yahoo! Finance of Google. .. code:: python %%time port = mean_variance_portfolio('am_tech_stocks', ma) # instantiates the portfolio class # and retrieves all the time series data needed .. parsed-literal:: CPU times: user 236 ms, sys: 3 ms, total: 239 ms Wall time: 1.2 s Basic Statistics ---------------- Since no **portfolio weights** have been provided, the class defaults to equal weights. .. code:: python port.get_weights() # defaults to equal weights .. parsed-literal:: {'AAPL': 0.25, 'FB': 0.25, 'GOOG': 0.25, 'MSFT': 0.25} Given these weights you can calculate the **portfolio return** via the method ``get_portfolio_return``. .. code:: python port.get_portfolio_return() # expected (= historical mean) return .. parsed-literal:: 0.19012032495905895 Analogously, you can call ``get_portfolio_variance`` to get the historical **portfolio variance**. .. code:: python port.get_portfolio_variance() # expected (= historical) variance .. parsed-literal:: 0.043145879775030503 The class also has a neatly printable ``string`` representation. .. code:: python print port # ret. con. is "return contribution" # given the mean return and the weight # of the security .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.190 volatility 0.208 Sharpe ratio 0.915 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.250 | 0.054 GOOG | 0.250 | 0.040 MSFT | 0.250 | 0.013 FB | 0.250 | 0.083 Setting Weights --------------- Via the method ``set_weights`` the weights of the single portfolio components can be adjusted. .. code:: python port.set_weights([0.6, 0.2, 0.1, 0.1]) .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.201 volatility 0.216 Sharpe ratio 0.927 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.600 | 0.130 GOOG | 0.200 | 0.032 MSFT | 0.100 | 0.005 FB | 0.100 | 0.033 You cal also easily **check results for different weights** with changing the attribute values of an object. .. code:: python port.test_weights([0.6, 0.2, 0.1, 0.1]) # returns av. return + vol + Sharp ratio # without setting new weights .. parsed-literal:: array([ 0.20064395, 0.21644491, 0.92699775]) Let us implement a **Monte Carlo simulation** over potential portfolio weights. .. code:: python # Monte Carlo simulation of portfolio compositions rets = [] vols = [] for w in range(500): weights = np.random.random(4) weights /= sum(weights) r, v, sr = port.test_weights(weights) rets.append(r) vols.append(v) rets = np.array(rets) vols = np.array(vols) And the simulation results **visualized**. .. code:: python import matplotlib.pyplot as plt %matplotlib inline plt.figure(figsize=(10, 6)) plt.scatter(vols, rets, c=rets / vols, marker='o') plt.xlabel('expected volatility') plt.ylabel('expected return') plt.colorbar(label='Sharpe ratio') .. parsed-literal:: .. image:: 11_dx_mean_variance_portfolio_files/11_dx_mean_variance_portfolio_27_1.png Optimizing Portfolio Composition -------------------------------- One of the major application areas of the mean-variance portfolio theory and therewith of this DX Analytics class it the **optimization of the portfolio composition**. Different target functions can be used to this end. Return ~~~~~~ The first target function might be the **portfolio return**. .. code:: python port.optimize('Return') # maximizes expected return of portfolio # no volatility constraint .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.332 volatility 0.523 Sharpe ratio 0.635 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.000 | 0.000 GOOG | -0.000 | 0.000 MSFT | 0.000 | 0.000 FB | 1.000 | 0.332 Instead of maximizing the portfolio return without any constraints, you can also set a (sensible/possible) **maximum target volatility** level as a constraint. Both, in an **exact sense** ("equality constraint") ... .. code:: python port.optimize('Return', constraint=0.225, constraint_type='Exact') # interpretes volatility constraint as equality .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.226 volatility 0.225 Sharpe ratio 1.005 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.481 | 0.104 GOOG | 0.294 | 0.047 MSFT | 0.000 | 0.000 FB | 0.225 | 0.075 ... or just a an **upper bound** ("inequality constraint"). .. code:: python port.optimize('Return', constraint=0.4, constraint_type='Bound') # interpretes volatility constraint as inequality (upper bound) .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.302 volatility 0.400 Sharpe ratio 0.756 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.258 | 0.056 GOOG | 0.000 | 0.000 MSFT | -0.000 | 0.000 FB | 0.742 | 0.246 Risk ~~~~ The class also allows you to minimize **portfolio risk**. .. code:: python port.optimize('Vol') # minimizes expected volatility of portfolio # no return constraint .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.140 volatility 0.187 Sharpe ratio 0.747 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.211 | 0.046 GOOG | 0.248 | 0.040 MSFT | 0.448 | 0.023 FB | 0.093 | 0.031 And, as before, to set **constraints** (in this case) for the target return level. .. code:: python port.optimize('Vol', constraint=0.175, constraint_type='Exact') # interpretes return constraint as equality .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.175 volatility 0.194 Sharpe ratio 0.904 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.318 | 0.069 GOOG | 0.282 | 0.045 MSFT | 0.256 | 0.013 FB | 0.143 | 0.048 .. code:: python port.optimize('Vol', constraint=0.20, constraint_type='Bound') # interpretes return constraint as inequality (upper bound) .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.200 volatility 0.206 Sharpe ratio 0.970 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.397 | 0.086 GOOG | 0.301 | 0.048 MSFT | 0.123 | 0.006 FB | 0.179 | 0.059 Sharpe Ratio ~~~~~~~~~~~~ Often, the target of the portfolio optimization efforts is the so called **Sharpe ratio**. The ``mean_variance_portfolio`` class of DX Analytics assumes a **risk-free rate of zero** in this context. .. code:: python port.optimize('Sharpe') # maximize Sharpe ratio .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.229 volatility 0.228 Sharpe ratio 1.006 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.493 | 0.107 GOOG | 0.268 | 0.043 MSFT | 0.000 | 0.000 FB | 0.238 | 0.079 Efficient Frontier ------------------ Another application area is to derive the **efficient frontier** in the mean-variance space. These are all these portfolios for which there is **no portfolio with both lower risk and higher return**. The method ``get_efficient_frontier`` yields the desired results. .. code:: python %%time evols, erets = port.get_efficient_frontier(100) # 100 points of the effient frontier .. parsed-literal:: CPU times: user 2.12 s, sys: 1 ms, total: 2.12 s Wall time: 2.12 s The plot with the **random and efficient portfolios**. .. code:: python plt.figure(figsize=(10, 6)) plt.scatter(vols, rets, c=rets / vols, marker='o') plt.scatter(evols, erets, c=erets / evols, marker='x') plt.xlabel('expected volatility') plt.ylabel('expected return') plt.colorbar(label='Sharpe ratio') .. parsed-literal:: .. image:: 11_dx_mean_variance_portfolio_files/11_dx_mean_variance_portfolio_57_1.png Capital Market Line ------------------- The **capital market line** is another key element of the mean-variance portfolio approach representing all those risk-return combinations (in mean-variance space) that are possible to form from a **risk-less money market account** and **the market portfolio** (or another appropriate substitute efficient portfolio). .. code:: python %%time cml, optv, optr = port.get_capital_market_line(riskless_asset=0.05) # capital market line for effiecient frontier and risk-less short rate .. parsed-literal:: CPU times: user 2.15 s, sys: 1 ms, total: 2.15 s Wall time: 2.15 s .. code:: python cml # lambda function for capital market line .. parsed-literal:: > The following plot illustrates that the capital market line has an ordinate value equal to the **risk-free rate** (the safe return of the money market account) and is tangent to the **efficient frontier**. .. code:: python plt.figure(figsize=(10, 6)) plt.plot(evols, erets, lw=2.0, label='efficient frontier') plt.plot((0, 0.4), (cml(0), cml(0.4)), lw=2.0, label='capital market line') plt.plot(optv, optr, 'r*', markersize=10, label='optimal portfolio') plt.legend(loc=0) plt.ylim(0) plt.xlabel('expected volatility') plt.ylabel('expected return') .. parsed-literal:: .. image:: 11_dx_mean_variance_portfolio_files/11_dx_mean_variance_portfolio_63_1.png Portfolio return and risk of the efficient portfolio used are: .. code:: python optr .. parsed-literal:: 0.23779804830709764 .. code:: python optv .. parsed-literal:: 0.23758786292012518 The **portfolio composition** can be derived as follows. .. code:: python port.optimize('Vol', constraint=optr, constraint_type='Exact') .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.238 volatility 0.238 Sharpe ratio 1.001 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.532 | 0.116 GOOG | 0.192 | 0.031 MSFT | 0.000 | 0.000 FB | 0.276 | 0.091 Or also in this way. .. code:: python port.optimize('Return', constraint=optv, constraint_type='Exact') .. code:: python print port .. parsed-literal:: Portfolio am_tech_stocks -------------------------- return 0.238 volatility 0.238 Sharpe ratio 1.001 Positions symbol | weight | ret. con. --------------------------- AAPL | 0.531 | 0.115 GOOG | 0.193 | 0.031 MSFT | 0.000 | 0.000 FB | 0.276 | 0.092 Dow Jones Industrial Average ---------------------------- As a larger, more realistic example, consider **all symbols of the Dow Jones Industrial Average 30 index**. .. code:: python symbols = ['AXP', 'BA', 'CAT', 'CSCO', 'CVX', 'DD', 'DIS', 'GE', 'GS', 'HD', 'IBM', 'INTC', 'JNJ', 'JPM', 'KO', 'MCD', 'MMM', 'MRK', 'MSFT', 'NKE', 'PFE', 'PG', 'T', 'TRV', 'UNH', 'UTX', 'V', 'VZ','WMT', 'XOM'] # all DJIA 30 symbols .. code:: python ma = market_environment('ma', dt.date(2010, 1, 1)) ma.add_list('symbols', symbols) ma.add_constant('source', 'google') ma.add_constant('final date', dt.date(2014, 3, 1)) **Data retrieval** in this case takes a bit. .. code:: python %%time djia = mean_variance_portfolio('djia', ma) # defining the portfolio and retrieving the data .. parsed-literal:: Can not find data for source google and symbol DIS. Will try other source. Can not find data for source google and symbol MMM. Will try other source. CPU times: user 1.48 s, sys: 36 ms, total: 1.51 s Wall time: 8.21 s .. code:: python %%time djia.optimize('Vol') print djia.variance, djia.variance ** 0.5 # minimium variance & volatility in decimals .. parsed-literal:: 0.00868910595895 0.0932153740482 CPU times: user 192 ms, sys: 1 ms, total: 193 ms Wall time: 192 ms Given the larger data set now used, **efficient frontier** ... .. code:: python %%time evols, erets = djia.get_efficient_frontier(25) # efficient frontier of DJIA .. parsed-literal:: CPU times: user 8.8 s, sys: 1 ms, total: 8.8 s Wall time: 8.8 s ... and **capital market line** derivations take also longer. .. code:: python %%time cml, optv, optr = djia.get_capital_market_line(riskless_asset=0.01) # capital market line and optimal (tangent) portfolio .. parsed-literal:: CPU times: user 31.6 s, sys: 1 ms, total: 31.6 s Wall time: 31.6 s .. code:: python plt.figure(figsize=(10, 6)) plt.plot(evols, erets, lw=2.0, label='efficient frontier') plt.plot((0, 0.4), (cml(0), cml(0.4)), lw=2.0, label='capital market line') plt.plot(optv, optr, 'r*', markersize=10, label='optimal portfolio') plt.legend(loc=0) plt.ylim(0) plt.xlabel('expected volatility') plt.ylabel('expected return') .. parsed-literal:: .. image:: 11_dx_mean_variance_portfolio_files/11_dx_mean_variance_portfolio_84_1.png **Copyright, License & Disclaimer** © Dr. Yves J. Hilpisch \| The Python Quants GmbH DX Analytics (the "dx library") is licensed under the GNU Affero General Public License version 3 or later (see http://www.gnu.org/licenses/). DX Analytics comes with no representations or warranties, to the extent permitted by applicable law. http://tpq.io \| team@tpq.io \| http://twitter.com/dyjh **Quant Platform** \| http://quant-platform.com **Derivatives Analytics with Python (Wiley Finance)** \| http://derivatives-analytics-with-python.com **Python for Finance (O'Reilly)** \| http://python-for-finance.com