From 9eb170c6cb0f83da3895f7db2906d6747789b32e Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Wed, 21 Jun 2023 15:25:33 -0700 Subject: [PATCH] 34 fix doc index (#35) * introduce bounds for factor weights * notebooks --- book/docs/index.md | 140 +++++- book/docs/notebooks/demo.ipynb | 507 ++++++++++----------- book/docs/notebooks/factormodel.ipynb | 123 ++++- book/docs/notebooks/large.ipynb | 32 +- book/docs/notebooks/sample.ipynb | 17 +- cvx/portfolio/__init__.py | 0 cvx/portfolio/min_risk.py | 14 + tests/test_risk/minvar.py | 25 +- tests/test_risk/test_cvar/test_cvar.py | 4 +- tests/test_risk/test_factor/test_factor.py | 13 +- tests/test_risk/test_sample/test_sample.py | 4 +- 11 files changed, 546 insertions(+), 333 deletions(-) create mode 100644 cvx/portfolio/__init__.py create mode 100644 cvx/portfolio/min_risk.py diff --git a/book/docs/index.md b/book/docs/index.md index d632860d..8a32fc40 100644 --- a/book/docs/index.md +++ b/book/docs/index.md @@ -1,3 +1,141 @@ # cvxrisk -[README](../../README.md) +[![PyPI version](https://badge.fury.io/py/cvxrisk.svg)](https://badge.fury.io/py/cvxrisk) +[![Apache 2.0 License](https://img.shields.io/badge/License-APACHEv2-brightgreen.svg)](https://github.com/cvxgrp/simulator/blob/master/LICENSE) +[![PyPI download month](https://img.shields.io/pypi/dm/cvxrisk.svg)](https://pypi.python.org/pypi/cvxrisk/) + +We provide an abstract `Model` class. +The class is designed to be used in conjunction with [cvxpy](https://github.com/cvxpy/cvxpy). +Using this class, we can formulate a function computing a standard minimum risk portfolio as + +```python +import cvxpy as cp + +from cvx.risk import Model + + +def minimum_risk(w: cp.Variable, risk_model: Model, **kwargs) -> cp.Problem: + """Constructs a minimum variance portfolio. + + Args: + w: cp.Variable representing the portfolio weights. + risk_model: A risk model. + + Returns: + A convex optimization problem. + """ + return cp.Problem( + cp.Minimize(risk_model.estimate(w, **kwargs)), + [cp.sum(w) == 1, w >= 0] + risk_model.constraints(w, **kwargs) + ) +``` + +The risk model is injected into the function. +The function is not aware of the precise risk model used. +All risk models are required to implement the `estimate` method. + +Note that factor risk models work with weights for the assets but also with weights for the factors. +To stay flexible we are applying thiS `**kwargs` pattern to the function above. +## A first example + +A first example is a risk model based on the sample covariance matrix. +We construct the risk model as follows + +```python +import numpy as np +import cvxpy as cp + +from cvx.risk.sample import SampleCovariance + +riskmodel = SampleCovariance(num=2) +w = cp.Variable(2) +problem = minimum_risk(w, riskmodel) + +riskmodel.update(cov=np.array([[1.0, 0.5], [0.5, 2.0]])) +problem.solve() +print(w.value) +``` + +The risk model and the actual optimization problem are decoupled. +This is good practice and keeps the code clean and maintainable. + +In a backtest we don't have to reconstruct the problem in every iteration. +We can simply update the risk model with the new data and solve the problem again. +The implementation of the risk models is flexible enough to deal with changing dimensions +of the underlying weight space. + +## Risk models + +### Sample covariance + +We offer a `SampleCovariance` class as seen above. + + +### Factor risk models + +Factor risk models use the projection of the weight vector into a lower +dimensional subspace, e.g. each asset is the linear combination of $k$ factors. +```math +r_i = \sum_{j=1}^k f_j \beta_{ji} + \epsilon_i +``` +The factor time series are $f_1, \ldots, f_k$. The loadings are the coefficients +$\beta_{ji}$. +The residual returns $\epsilon_i$ are assumed to be uncorrelated with the factors. + +Any position $w$ in weight space projects to a position $y = \beta^T w$ in factor space. +The variance for a position $w$ is the sum of the variance of the +systematic returns explained by the factors and the variance of the idiosyncratic returns. + +```math +Var(r) = Var(\beta^T w) + Var(\epsilon w) +``` + +We assume the residual returns are uncorrelated and hence + +```math +Var(r) = y^T \Sigma_f y + \sum_i w_i^2 Var(\epsilon_i) +``` + +where $\Sigma_f$ is the covariance matrix of the factors and $Var(\epsilon_i)$ +is the variance of the idiosyncratic returns. + +Factor risk models are widely used in practice. Usually two scenarios are distinguished. +A first route is to rely on estimates for the factor covariance matrix $\Sigma_f$, +the loadings $\beta$ and the volatilities of the idiosyncratic returns $\epsilon_i$. +Usually those quantities are provided by external parties, e.g. Barra or Axioma. + +An alternative would be to start with the estimation of factor time series $f_1, \ldots, f_k$. +Usually they are estimated via a principal component analysis (PCA) of the asset returns. +It is then a simple linear regression to compute the loadings $\beta$. +The volatilities of the idiosyncratic returns $\epsilon_i$ are computed as the standard deviation +of the observed residuals. +The factor covariance matrix $\Sigma_f$ may even be diagonal in this case as the factors are orthogonal. + +We expose a method to compute the first $k$ principal components. + +### cvar + +We currently also support the conditional value at risk (CVaR) as a risk measure. + + + + +## Poetry + +We assume you share already the love for [Poetry](https://python-poetry.org). Once you have installed poetry you can perform + +```bash +poetry install +``` + +to replicate the virtual environment we have defined in pyproject.toml. + +## Kernel + +We install [JupyterLab](https://jupyter.org) within your new virtual environment. Executing + +```bash +./create_kernel.sh +``` + +constructs a dedicated [Kernel](https://docs.jupyter.org/en/latest/projects/kernels.html) for the project. diff --git a/book/docs/notebooks/demo.ipynb b/book/docs/notebooks/demo.ipynb index 3dd0706e..ad5e5dd3 100644 --- a/book/docs/notebooks/demo.ipynb +++ b/book/docs/notebooks/demo.ipynb @@ -54,6 +54,7 @@ "\n", "from cvx.simulator.builder import builder\n", "from cvx.risk.sample import SampleCovariance\n", + "from cvx.portfolio.min_risk import minrisk_problem\n", "\n", "pd.options.plotting.backend = \"plotly\"" ] @@ -130,43 +131,6 @@ { "cell_type": "code", "execution_count": 5, - "metadata": { - "ExecuteTime": { - "end_time": "2023-06-15T22:54:53.131156925Z", - "start_time": "2023-06-15T22:54:53.087404803Z" - }, - "collapsed": false, - "jupyter": { - "outputs_hidden": false - }, - "pycharm": { - "name": "#%%\n" - } - }, - "outputs": [], - "source": [ - "# Solver for minimum risk portfolio\n", - "from cvx.risk import RiskModel\n", - "\n", - "def minimum_risk(w: cp.Variable, risk_model: RiskModel) -> cp.Problem:\n", - " \"\"\"Constructs a minimum variance portfolio.\n", - "\n", - " Args:\n", - " w: cp.Variable representing the portfolio weights.\n", - " risk_model: A risk model.\n", - "\n", - " Returns:\n", - " A convex optimization problem.\n", - " \"\"\"\n", - " return cp.Problem(\n", - " cp.Minimize(risk_model.estimate(w)),\n", - " [cp.sum(w) == 1, w >= 0] + risk_model.bounds.constraints(w)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2023-06-15T22:54:54.569493271Z", @@ -186,11 +150,11 @@ "_builder = builder(prices=prices.truncate(before=start), initial_cash=1e6)\n", "\n", "w = cp.Variable(len(_builder.assets))\n", + "problem = minrisk_problem(risk_model, w)\n", "\n", - "problem = minimum_risk(w, risk_model)\n", "\n", "for t, _ in _builder:\n", - " risk_model.update(cov=cov.loc[t[-1]].values)\n", + " risk_model.update(cov=cov.loc[t[-1]].values, lower_assets=np.zeros(20), upper_assets=np.ones(20))\n", " \n", " # don't reconstruct the problem in every iteration!\n", " problem.solve()\n", @@ -202,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": { "ExecuteTime": { "end_time": "2023-06-15T22:54:54.981957450Z", @@ -493,225 +457,225 @@ "xaxis": "x", "y": [ 1000000, - 999832.7262823234, - 1003669.5137637139, - 1008795.5559812981, - 1007222.1126431926, - 1009331.022555837, - 1005851.0269626664, - 1004701.1381720055, - 1003116.384937654, - 1010787.5426461268, - 1008134.7563470246, - 1008617.7123914324, - 1006101.3419620049, - 1003645.2003508218, - 1005551.2340404729, - 1010374.8596093112, - 1007114.9544301655, - 1009040.7040543002, - 1006723.0440464285, - 1009359.9747484045, - 1007165.3655508234, - 999826.9767677613, - 1007166.4117322675, - 996639.0822432181, - 995272.5141985622, - 999253.0173801879, - 997064.8949164951, - 987161.6687241734, - 990346.5666775684, - 987834.5998619014, - 988209.8755898443, - 994959.7128165235, - 995511.2606292936, - 1001917.8489271363, - 1000744.0580153094, - 1001379.4306290307, - 1004068.5719848042, - 1005069.0704607473, - 1001932.0836696402, - 1002897.14254395, - 1007958.3013184445, - 1016064.2301769946, - 1023015.2438274482, - 1014061.1089045323, - 1011119.3585876531, - 1013202.6099239368, - 1015003.2478743118, - 1018432.2233624714, - 1019927.523508457, - 1020150.9834549428, - 1018695.2932170236, - 1020804.6001778949, - 1011290.5963872717, - 1008657.9861715386, - 1014262.6630239614, - 1014694.1914222458, - 1013886.8372285452, - 999140.0910145184, - 997822.8018944291, - 998954.3760948512, - 1009812.9881858769, - 1009963.6802636856, - 1004403.4531210638, - 1006322.0234461875, - 1004374.2336675012, - 1006434.7814555115, - 1006049.2174935233, - 1011181.2225585344, - 1012973.7859697356, - 1014131.5522517455, - 1020827.2096727086, - 1024000.0986243852, - 1020333.4580913623, - 1030594.9639287546, - 1044760.6839513367, - 1045566.1441962812, - 1049059.4480874194, - 1049728.4928648109, - 1051012.7474906591, - 1054737.3733338108, - 1062042.3698800374, - 1057108.8718637365, - 1056801.1154860607, - 1053624.2227769643, - 1051647.4259610604, - 1058565.6803594886, - 1062177.1392175085, - 1062227.5992582117, - 1064217.4744897427, - 1070348.4805612294, - 1067252.3797321944, - 1072973.8780838735, - 1071229.3895542072, - 1075051.7921710913, - 1085028.201063718, - 1089814.6397026333, - 1081390.5801750566, - 1081761.0475914136, - 1082374.490129528, - 1083358.807835462, - 1080592.1059586937, - 1081976.890292695, - 1086578.5641130265, - 1080057.0221041418, - 1079363.142307646, - 1074327.1667792567, - 1074579.002380759, - 1083058.7078961695, - 1078889.1158968827, - 1078697.3839155613, - 1083096.1593599098, - 1083862.8241840885, - 1088068.0154731278, - 1089999.4848501976, - 1090502.0561013597, - 1092921.2108470968, - 1094730.9773191852, - 1095241.7882664541, - 1091855.6856510951, - 1086808.2846059427, - 1079345.2985238703, - 1091776.9586989395, - 1086247.707925965, - 1088857.2127179988, - 1095268.6244972034, - 1096124.6963974684, - 1098520.6331642203, - 1094841.0192678422, - 1104682.7102529313, - 1108713.1741979166, - 1115537.7387418803, - 1114558.782794502, - 1112543.3672034768, - 1105989.666241704, - 1103852.2291959228, - 1107165.1954667862, - 1111910.164893597, - 1120010.842261801, - 1120916.1126626874, - 1124491.3762810268, - 1121345.8146509682, - 1128771.5202933734, - 1130610.974662067, - 1124968.9867078094, - 1123107.1600995548, - 1126703.2802120685, - 1125820.7568764528, - 1125741.764984746, - 1125604.0899841096, - 1127858.2815375822, - 1123118.7968157944, - 1137874.2904884294, - 1152818.7743469542, - 1156712.3061198967, - 1164085.1016866816, - 1164810.5972695858, - 1161738.7568400174, - 1159359.5798665965, - 1165928.1874076878, - 1174581.3708718712, - 1171518.087043379, - 1184106.1774472967, - 1184236.6215794221, - 1185360.409072535, - 1195911.8910874715, - 1197754.3493949366, - 1194104.3250427623, - 1196685.1338338298, - 1210255.4161720425, - 1200255.3589868047, - 1185700.583036005, - 1183576.754963565, - 1194260.4424971286, - 1169808.5511647167, - 1124783.51598032, - 1150501.2809148887, - 1139779.1359924716, - 1105642.122572659, - 1117213.0595198357, - 1132175.3672262873, - 1139789.3763784915, - 1147180.0213388796, - 1157508.6630399434, - 1161352.372211209, - 1140170.463672183, - 1136102.1126385736, - 1134875.9276287132, - 1151143.9961392644, - 1171566.0434872024, - 1155975.5311457631, - 1149566.5648314352, - 1134774.8070265907, - 1142924.1483044417, - 1152163.7137766704, - 1154500.5875655431, - 1146762.227431614, - 1153111.3494389905, - 1166179.582856886, - 1176452.8747107459, - 1171122.881218968, - 1162930.9253061009, - 1169302.179100539, - 1169694.5176287973, - 1153937.7095484864, - 1160172.6851737355, - 1158676.1777162245, - 1139438.1482151432, - 1114195.2577217538, - 1137820.2213254166, - 1130585.8279604325, - 1134831.3934908798, - 1143808.760521818, - 1115789.460287325, - 1140165.8732478912, - 1153617.1450409275, - 1161156.1933425826, - 1142958.3081733305, - 1147018.3373238002, - 1167556.6406650604, - 1162755.978231735 + 999832.7262823227, + 1003669.5137637099, + 1008795.5559812931, + 1007222.112643187, + 1009331.0225558258, + 1005851.0269626555, + 1004701.1381719933, + 1003116.384937617, + 1010787.542646087, + 1008134.7563469844, + 1008617.7123914021, + 1006101.3419619747, + 1003645.2003507904, + 1005551.2340404105, + 1010374.8596092472, + 1007114.9544301011, + 1009040.7040542326, + 1006723.0440463503, + 1009359.9747483154, + 1007165.3655507355, + 999826.9767676707, + 1007166.4117321761, + 996639.0822431274, + 995272.5141984713, + 999253.0173800966, + 997064.8949164039, + 987161.6687240846, + 990346.5666774792, + 987834.5998618101, + 988209.8755897547, + 994959.712816435, + 995511.2606292049, + 1001917.8489270473, + 1000744.0580152204, + 1001379.4306289416, + 1004068.5719847142, + 1005069.0704606386, + 1001932.0836695262, + 1002897.1425438359, + 1007958.3013183307, + 1016064.2301768766, + 1023015.2438273258, + 1014061.1089044097, + 1011119.3585875119, + 1013202.6099237959, + 1015003.2478741806, + 1018432.2233623394, + 1019927.5235083264, + 1020150.9834548088, + 1018695.2932168898, + 1020804.6001777475, + 1011290.5963871252, + 1008657.9861713924, + 1014262.6630238143, + 1014694.1914220967, + 1013886.8372283976, + 999140.0910143725, + 997822.8018942848, + 998954.3760947022, + 1009812.9881857322, + 1009963.6802635432, + 1004403.4531209226, + 1006322.0234460436, + 1004374.2336673621, + 1006434.7814553719, + 1006049.2174933825, + 1011181.2225583927, + 1012973.7859695929, + 1014131.5522516018, + 1020827.2096725643, + 1024000.0986242407, + 1020333.4580912194, + 1030594.9639286101, + 1044760.6839511736, + 1045566.1441961182, + 1049059.4480872557, + 1049728.4928646472, + 1051012.7474904952, + 1054737.3733336462, + 1062042.369879872, + 1057108.8718635547, + 1056801.11548588, + 1053624.2227767743, + 1051647.4259608686, + 1058565.6803592946, + 1062177.1392173138, + 1062227.5992580173, + 1064217.4744895478, + 1070348.4805610315, + 1067252.3797319965, + 1072973.8780836747, + 1071229.3895540116, + 1075051.7921708925, + 1085028.2010635175, + 1089814.6397024319, + 1081390.5801748573, + 1081761.0475912143, + 1082374.4901293288, + 1083358.8078352674, + 1080592.1059585032, + 1081976.890292504, + 1086578.5641128346, + 1080057.0221039483, + 1079363.1423074561, + 1074327.1667790571, + 1074579.0023805597, + 1083058.7078960002, + 1078889.1158967146, + 1078697.383915394, + 1083096.159359742, + 1083862.8241839292, + 1088068.015472968, + 1089999.484850036, + 1090502.056101204, + 1092921.210846941, + 1094730.977319031, + 1095241.7882662993, + 1091855.6856509405, + 1086808.284605788, + 1079345.298523717, + 1091776.9586987891, + 1086247.7079258007, + 1088857.2127178344, + 1095268.6244970383, + 1096124.69639727, + 1098520.6331640217, + 1094841.0192676496, + 1104682.7102527376, + 1108713.1741977083, + 1115537.7387416707, + 1114558.782794293, + 1112543.3672033628, + 1105989.6662415902, + 1103852.229195809, + 1107165.1954666297, + 1111910.1648934414, + 1120010.8422616236, + 1120916.1126627536, + 1124491.3762810514, + 1121345.8146509987, + 1128771.520293419, + 1130610.9746621135, + 1124968.986707856, + 1123107.1600996135, + 1126703.2802121267, + 1125820.756876511, + 1125741.7649848042, + 1125604.0899841674, + 1127858.28153764, + 1123118.7968158522, + 1137874.2904884676, + 1152818.7743469938, + 1156712.3061199365, + 1164085.101686722, + 1164810.5972696273, + 1161738.7568400588, + 1159359.5798666398, + 1165928.187407732, + 1174581.3708719208, + 1171518.0870434456, + 1184106.1774474096, + 1184236.6215795344, + 1185360.409072648, + 1195911.891087586, + 1197754.3493950516, + 1194104.3250428769, + 1196685.1338339688, + 1210255.416172186, + 1200255.35898693, + 1185700.5830361089, + 1183576.7549636723, + 1194260.4424972278, + 1169808.5511649158, + 1124783.515980503, + 1150501.2809150654, + 1139779.1359926502, + 1105642.1225729673, + 1117213.0595201533, + 1132175.3672266107, + 1139789.3763788207, + 1147180.0213392049, + 1157508.6630402636, + 1161352.372211531, + 1140170.4636724861, + 1136102.1126388751, + 1134875.9276290126, + 1151143.9961395734, + 1171566.043487517, + 1155975.5311460732, + 1149566.5648317442, + 1134774.8070267565, + 1142924.1483046245, + 1152163.7137768543, + 1154500.5875657261, + 1146762.2274317974, + 1153111.3494391772, + 1166179.5828570798, + 1176452.874710942, + 1171122.881219162, + 1162930.9253063006, + 1169302.17910074, + 1169694.5176289885, + 1153937.7095486675, + 1160172.685173918, + 1158676.1777164058, + 1139438.1482153207, + 1114195.257721929, + 1137820.2213255963, + 1130585.8279606109, + 1134831.3934912025, + 1143808.7605221188, + 1115789.460287354, + 1140165.8732479222, + 1153617.1450409568, + 1161156.193342615, + 1142958.3081731626, + 1147018.3373236333, + 1167556.6406648871, + 1162755.9782315623 ], "yaxis": "y" } @@ -1567,8 +1531,8 @@ 1 ], "range": [ - 974767.5716437363, - 1222649.5132524797 + 974767.5716436346, + 1222649.513252636 ], "title": { "text": "value" @@ -1578,9 +1542,9 @@ } }, "text/html": [ - "