├── .gitignore ├── LICENSE ├── Portfolio optimization.ipynb ├── README.md ├── efrontier.png ├── environment.yaml ├── gdp_fred.csv ├── gold_fred.csv ├── requirements.txt └── transmap.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Druce Vertes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portfolio optimization with CVXPY 2 | 3 | Do a few classic portfolio optimizations using: 4 | 5 | - [CVXPY](https://www.cvxpy.org) ([paper](https://arxiv.org/abs/1603.00943)), a modeling environment for [convex optimization](https://web.stanford.edu/~boyd/cvxbook/), supporting [many back-end solvers](https://www.cvxpy.org/tutorial/advanced/index.html#solve-method-options). 6 | - Data (mostly) from [Prof. Aswath Damodaran](http://pages.stern.nyu.edu/~adamodar/New_Home_Page/datacurrent.html) and [FRED](https://fred.stlouisfed.org/) 7 | 8 | ![Efficient Frontier](efrontier.png) 9 | 10 | ![Optimal portfolio transition map](transmap.png) 11 | 12 | ## Online data 13 | 14 | - [Aswath Damodaran](https://pages.stern.nyu.edu/~adamodar/). See the [FAQ tab in his XLS](https://www.stern.nyu.edu/~adamodar/pc/datasets/histretSP.xls) for details on individual asset returns. 15 | - [FRED](https://fred.stlouisfed.org/) 16 | - [Shiller data](http://www.econ.yale.edu/~shiller/data.htm) 17 | - [Ken French](https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html) 18 | - [Macrotrends](https://www.macrotrends.net/) 19 | 20 | Currently use Damodaran data for returns. 21 | 22 | ## Steps 23 | 24 | 1. Load asset return data from Damodaran website using `pd.read_excel`. 25 | 2. Load GDP data from FRED using `pandas_datareader` module. 26 | 3. Compute covariance matrix, long-only efficient frontier, and transition map using historical data (see above). Also compute same outputs for 1972-present (post-gold standard) and 1983-present (post-inflation era). 27 | 4. Compute long-short efficient frontier and transition map, adding a random short asset with 5% annualized negative return and 90% correlation to S&P, and addig a 150% gross exposure constraint. 28 | 5. Compute some allocations using hierarchical risk parity model for comparison. 29 | 6. Compute an efficient frontier using a factor model, using a random set of returns for 1000 stocks and 10 random factor exposures and a random factor covariance matrix. 30 | 31 | 32 | This mostly follows the [cvxpy tutorial](https://colab.research.google.com/github/cvxgrp/cvx_short_course/blob/master/applications/portfolio_optimization.ipynb) but uses real historical data, and visualizes the full efficient frontier and transition map. 33 | 34 | #### Takeaways 35 | 36 | - Gold adds some value for most portfolios, except in most disinflationary environment at higher risk tolerances. 37 | - TIPS should be a more direct inflation hedge with a US government guaranteed real return but we don't have data back very far. 38 | - If you can find good shorts and use leverage, you can supercharge returns. 39 | 40 | ## Setup 41 | 42 | 1. git clone this repo, cd to repo directory 43 | 44 | 2. Install [Anaconda](https://www.anaconda.com/products/individual) 45 | 46 | 3. Create virtual environment 47 | ``` 48 | conda create -n portfolio_opt 49 | conda activate portfolio_opt 50 | pip install -r requirements.txt 51 | ``` 52 | 53 | or 54 | 55 | ``` 56 | conda env create -f environment.yaml 57 | conda activate portfolio_opt 58 | ``` 59 | 4. `jupyter notebook` 60 | 61 | 5. Run `Portfolio optimization.ipynb` 62 | 63 | ## Further reading 64 | 65 | - [CVXPY tutorial](https://www.cvxpy.org/version/1.1/tutorial/index.html) 66 | - [Convex optimization short course](https://stanford.edu/~boyd/papers/cvx_short_course.html) 67 | 68 | Asset allocation books: 69 | 70 | - [David Swensen - Pioneering Portfolio Management: An Unconventional Approach to Institutional Investment](https://www.amazon.com/Pioneering-Portfolio-Management-Unconventional-Institutional/dp/1416544690) 71 | - [Bernstein - The Intelligent Asset Allocator: How to Build Your Portfolio to Maximize Returns and Minimize Risk ](https://www.amazon.com/Intelligent-Asset-Allocator-Portfolio-Maximize/dp/1260026647) 72 | - [Kinlaw et al - Asset Allocation: From Theory to Practice and Beyond (Wiley Finance) 1st Edition](https://www.amazon.com/Asset-Allocation-Theory-Practice-Finance/dp/1119817714) 73 | - [Fabozzi and Markowitz, ed. - The Theory and Practice of Investment Management: Asset Allocation, Valuation, Portfolio Construction, and Strategies](https://www.amazon.com/Theory-Practice-Investment-Management-Construction/dp/0470929901) 74 | - [Muralidhar - Innovations in Pension Fund Management 1st Edition](https://www.amazon.com/Innovations-Pension-Fund-Management-Muralidhar/dp/0804745218) 75 | - [Ferri - All About Asset Allocation Paperback ](https://www.amazon.com/About-Asset-Allocation-Richard-Ferri/dp/0071429581) 76 | - [Faber - Global Asset Allocation: A Survey of the World's Top Asset Allocation Strategies](https://www.amazon.com/Global-Asset-Allocation-Survey-Strategies/dp/0988679922) 77 | -------------------------------------------------------------------------------- /efrontier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/druce/portfolio_optimization/1a4d126a3c68ae9c52e6e32a0cab9cbdedc7d3d8/efrontier.png -------------------------------------------------------------------------------- /environment.yaml: -------------------------------------------------------------------------------- 1 | name: portfolio_opt 2 | channels: 3 | - defaults 4 | dependencies: 5 | - ipython==7.13.0 6 | - ipykernel==5.1.4 7 | - jupyter==1.0.0 8 | - numpy==1.19.2 9 | - pandas==1.1.3 10 | - pandas-datareader==0.9.0 11 | - pip==20.0.2 12 | - seaborn==0.11.0 13 | - xlrd==1.2.0 14 | - pip: 15 | - cvxpy==1.1.7 16 | 17 | 18 | -------------------------------------------------------------------------------- /gdp_fred.csv: -------------------------------------------------------------------------------- 1 | DATE,DATE,GDPCA,GDP 2 | 1928,,,0.011 3 | 1929,1929-01-01,1191.124,0.0652 4 | 1930,1930-01-01,1089.785,-0.08507846370319128 5 | 1931,1931-01-01,1019.977,-0.06405667172882734 6 | 1932,1932-01-01,888.414,-0.12898624184662988 7 | 1933,1933-01-01,877.431,-0.012362479654755454 8 | 1934,1934-01-01,972.263,0.10807915380240729 9 | 1935,1935-01-01,1058.836,0.08904277957713091 10 | 1936,1936-01-01,1195.251,0.1288348715004024 11 | 1937,1937-01-01,1256.503,0.05124613993211469 12 | 1938,1938-01-01,1214.869,-0.033134819415472916 13 | 1939,1939-01-01,1312.365,0.08025227411350522 14 | 1940,1940-01-01,1428.075,0.08816906881850706 15 | 1941,1941-01-01,1681.049,0.17714335731666742 16 | 1942,1942-01-01,1998.542,0.18886599974182783 17 | 1943,1943-01-01,2338.761,0.17023360029461476 18 | 1944,1944-01-01,2524.752,0.0795254410348043 19 | 1945,1945-01-01,2500.057,-0.009781158703904502 20 | 1946,1946-01-01,2209.911,-0.11605575392881029 21 | 1947,1947-01-01,2184.614,-0.011447067325335736 22 | 1948,1948-01-01,2274.627,0.04120315991749579 23 | 1949,1949-01-01,2261.928,-0.0055828933710889705 24 | 1950,1950-01-01,2458.532,0.08691877018189809 25 | 1951,1951-01-01,2656.32,0.08044963417193673 26 | 1952,1952-01-01,2764.803,0.04083958258041198 27 | 1953,1953-01-01,2894.411,0.046877842652803814 28 | 1954,1954-01-01,2877.708,-0.005770776852354387 29 | 1955,1955-01-01,3083.026,0.07134775314243136 30 | 1956,1956-01-01,3148.765,0.021322882129440446 31 | 1957,1957-01-01,3215.065,0.021055874287220666 32 | 1958,1958-01-01,3191.216,-0.007417890462556809 33 | 1959,1959-01-01,3412.421,0.06931683721816384 34 | 1960,1960-01-01,3500.272,0.025744478773281454 35 | 1961,1961-01-01,3590.066,0.025653434933056607 36 | 1962,1962-01-01,3810.124,0.06129636613922984 37 | 1963,1963-01-01,3976.142,0.04357286009589201 38 | 1964,1964-01-01,4205.277,0.05762746903908367 39 | 1965,1965-01-01,4478.555,0.06498454204086923 40 | 1966,1966-01-01,4773.931,0.06595341577808012 41 | 1967,1967-01-01,4904.864,0.027426663686592967 42 | 1968,1968-01-01,5145.914,0.04914509352349028 43 | 1969,1969-01-01,5306.594,0.03122477367480303 44 | 1970,1970-01-01,5316.391,0.0018461936225004916 45 | 1971,1971-01-01,5491.445,0.03292722450248675 46 | 1972,1972-01-01,5780.048,0.05255501967150722 47 | 1973,1973-01-01,6106.371,0.05645679759060829 48 | 1974,1974-01-01,6073.363,-0.005405501893022802 49 | 1975,1975-01-01,6060.875,-0.0020561919318835553 50 | 1976,1976-01-01,6387.437,0.05388033905995426 51 | 1977,1977-01-01,6682.804,0.04624186508610584 52 | 1978,1978-01-01,7052.711,0.05535206479196453 53 | 1979,1979-01-01,7275.999,0.03165988227789285 54 | 1980,1980-01-01,7257.316,-0.0025677573622536753 55 | 1981,1981-01-01,7441.485,0.025377012658674314 56 | 1982,1982-01-01,7307.314,-0.018030137801796187 57 | 1983,1983-01-01,7642.266,0.0458379097983197 58 | 1984,1984-01-01,8195.295,0.07236453167162726 59 | 1985,1985-01-01,8537.004,0.04169575347806287 60 | 1986,1986-01-01,8832.611,0.03462655048539287 61 | 1987,1987-01-01,9137.745,0.03454629667263731 62 | 1988,1988-01-01,9519.427,0.04176982395547246 63 | 1989,1989-01-01,9869.003,0.03672237835323511 64 | 1990,1990-01-01,10055.129,0.018859655833522337 65 | 1991,1991-01-01,10044.238,-0.0010831288191331945 66 | 1992,1992-01-01,10398.046,0.03522497176988448 67 | 1993,1993-01-01,10684.179,0.02751795866261797 68 | 1994,1994-01-01,11114.647,0.04029022726032583 69 | 1995,1995-01-01,11413.012,0.026844307336076456 70 | 1996,1996-01-01,11843.599,0.03772772691380677 71 | 1997,1997-01-01,12370.299,0.04447127938053286 72 | 1998,1998-01-01,12924.876,0.04483133350293311 73 | 1999,1999-01-01,13543.774,0.04788425049493705 74 | 2000,2000-01-01,14096.033,0.04077585760069535 75 | 2001,2001-01-01,14230.726,0.009555383418866858 76 | 2002,2002-01-01,14472.712,0.01700447327845378 77 | 2003,2003-01-01,14877.312,0.027956059652123333 78 | 2004,2004-01-01,15449.757,0.03847771694241531 79 | 2005,2005-01-01,15987.957,0.03483549935445596 80 | 2006,2006-01-01,16433.148,0.027845396381789067 81 | 2007,2007-01-01,16762.445,0.020038582990915543 82 | 2008,2008-01-01,16781.485,0.0011358724816099564 83 | 2009,2009-01-01,16349.11,-0.02576500232250012 84 | 2010,2010-01-01,16789.75,0.026951925823485157 85 | 2011,2011-01-01,17052.41,0.015644068553730683 86 | 2012,2012-01-01,17442.759,0.02289113386318986 87 | 2013,2013-01-01,17812.167,0.021178300978647036 88 | 2014,2014-01-01,18261.714,0.025238198137262025 89 | 2015,2015-01-01,18799.622,0.02945550455997714 90 | 2016,2016-01-01,19141.672,0.01819451476205214 91 | 2017,2017-01-01,19612.102,0.024576223017508614 92 | 2018,2018-01-01,20193.896,0.02966505069165981 93 | 2019,2019-01-01,20715.671,0.025838253301888825 94 | 2020,2020-01-01,20267.585,-0.02163029138665118 95 | 2021,2021-01-01,21494.798,0.06055052933045557 96 | 2022,2022-01-01,22034.828,0.025123753198332155 97 | 2023,2023-01-01,22671.096,0.02887556009059833 98 | -------------------------------------------------------------------------------- /gold_fred.csv: -------------------------------------------------------------------------------- 1 | Year,gold 2 | 1928, 3 | 1929, 4 | 1930, 5 | 1931, 6 | 1932, 7 | 1933, 8 | 1934, 9 | 1935, 10 | 1936, 11 | 1937, 12 | 1938, 13 | 1939, 14 | 1940, 15 | 1941, 16 | 1942, 17 | 1943, 18 | 1944, 19 | 1945, 20 | 1946, 21 | 1947, 22 | 1948, 23 | 1949, 24 | 1950, 25 | 1951, 26 | 1952, 27 | 1953, 28 | 1954, 29 | 1955, 30 | 1956, 31 | 1957, 32 | 1958, 33 | 1959, 34 | 1960, 35 | 1961, 36 | 1962, 37 | 1963, 38 | 1964, 39 | 1965, 40 | 1966, 41 | 1967, 42 | 1968, 43 | 1969,-0.15990453460620513 44 | 1970,0.061931818181818254 45 | 1971,0.1669341894060994 46 | 1972,0.4878496102705183 47 | 1973,0.7295839753466871 48 | 1974,0.6614699331848553 49 | 1975,-0.24798927613941024 50 | 1976,-0.04099821746880572 51 | 1977,0.2263940520446095 52 | 1978,0.3701121551985451 53 | 1979,1.2654867256637168 54 | 1980,0.15185546875 55 | 1981,-0.3259855871131836 56 | 1982,0.14943396226415095 57 | 1983,-0.16305537316699492 58 | 1984,-0.193776150627615 59 | 1985,0.0600064871878041 60 | 1986,0.18956548347613222 61 | 1987,0.24527331189710622 62 | 1988,-0.1525511258004545 63 | 1989,-0.028397318708104802 64 | 1990,-0.031108881083793394 65 | 1991,-0.08557742102537547 66 | 1992,-0.057341073198357684 67 | 1993,0.17677981375788532 68 | 1994,-0.02169751116783658 69 | 1995,0.009784735812133016 70 | 1996,-0.04586563307493541 71 | 1997,-0.21408259986459044 72 | 1998,-0.008270158511371362 73 | 1999,0.00851285615010422 74 | 2000,-0.05443583118001727 75 | 2001,0.007469484423392236 76 | 2002,0.2556962025316456 77 | 2003,0.1988767281105992 78 | 2004,0.046486486486486456 79 | 2005,0.1776859504132231 80 | 2006,0.23196881091617927 81 | 2007,0.3192246835443038 82 | 2008,0.04317841079460272 83 | 2009,0.25035929864903705 84 | 2010,0.2924137931034483 85 | 2011,0.08929206688011382 86 | 2012,0.08262573481384705 87 | 2013,-0.2733031674208145 88 | 2014,0.001245330012453305 89 | 2015,-0.12106135986733002 90 | 2016,0.0810377358490566 91 | 2017,0.12662535997905566 92 | 2018,-0.009295120061967421 93 | 2019,0.1843236903831118 94 | 2020,0.24614622875061887 95 | 2021,-0.043308963763509234 96 | 2022,0.004374671207464598 97 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipython 2 | ipykernel 3 | jupyter 4 | numpy 5 | pandas 6 | pandas-datareader 7 | seaborn 8 | matplotlib 9 | xlrd 10 | cvxpy 11 | riskfolio-lib 12 | python-dotenv 13 | yapf # jupyter pretty-print 14 | openbb[all] 15 | 16 | 17 | -------------------------------------------------------------------------------- /transmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/druce/portfolio_optimization/1a4d126a3c68ae9c52e6e32a0cab9cbdedc7d3d8/transmap.png --------------------------------------------------------------------------------