1+ #include < cstring>
2+ #include < Eigen/Dense>
13#include < pybind11/pybind11.h>
24#include < pybind11/eigen.h>
35
46using namespace Eigen ;
57using namespace std ;
68
9+ typedef const Eigen::Matrix<double , Eigen::Dynamic, 1 > c_vector_t ;
10+ typedef const Eigen::Matrix<double , Eigen::Dynamic, Eigen::Dynamic> c_matrix_t ;
11+ typedef Eigen::Matrix<double , Eigen::Dynamic, 1 > vector_t ;
12+ typedef Eigen::Matrix<double , Eigen::Dynamic, Eigen::Dynamic> matrix_t ;
13+
714// Cyclical coordinate descent for Spinu's formulation
815// of the risk parity portfolio problem
9- Eigen::VectorXd risk_parity_portfolio_ccd_spinu (const Eigen::MatrixXd & Sigma,
10- const Eigen::VectorXd & b,
11- const double tol = 1E-8 ,
12- const unsigned int maxiter = 50 ) {
16+ vector_t risk_parity_portfolio_ccd_spinu (c_matrix_t & Sigma,
17+ c_vector_t & b,
18+ const double tol = 1E-4 ,
19+ const unsigned int maxiter = 100 ) {
1320 double aux, x_diff, xk_sum;
1421 const unsigned int n = b.size ();
15- Eigen::VectorXd xk = (1 / Sigma.diagonal ().array ().sqrt ()).matrix ();
16- Eigen::VectorXd x_star (n), Sigma_xk (n), rc (n);
22+ vector_t xk = (1 / Sigma.diagonal ().array ().sqrt ()).matrix ();
23+ vector_t x_star (n), Sigma_xk (n), rc (n);
1724 Sigma_xk = Sigma * xk;
1825 for (unsigned int k = 0 ; k < maxiter; ++k) {
1926 for (unsigned int i = 0 ; i < n; ++i) {
@@ -34,14 +41,56 @@ Eigen::VectorXd risk_parity_portfolio_ccd_spinu(const Eigen::MatrixXd& Sigma,
3441}
3542
3643
44+ // cyclical coordinate descent algo by Choi & Chen 2022
45+ // ref: https://arxiv.org/pdf/2203.00148.pdf
46+ vector_t risk_parity_portfolio_ccd_choi (c_matrix_t & cov,
47+ c_vector_t & b,
48+ const double tol = 1E-4 ,
49+ const unsigned int maxiter = 100 ) {
50+ const unsigned int n = b.size ();
51+ vector_t a (n);
52+ vector_t vol = cov.diagonal ().array ().sqrt ();
53+ matrix_t invvol_mat = (1 / vol.array ()).matrix ().asDiagonal ();
54+ matrix_t corr = invvol_mat * cov * invvol_mat;
55+ matrix_t adj = corr;
56+ adj.diagonal ().array () = 0 ;
57+ vector_t wk = vector_t::Ones (n);
58+ wk = (wk.array () / std::sqrt (corr.sum ())).matrix ();
59+ for (unsigned int k = 0 ; k < maxiter; ++k) {
60+ // compute portfolio weights
61+ a = 0.5 * adj * wk;
62+ wk = ((a.array () * a.array () + b.array ()).sqrt () - a.array ()).matrix ();
63+ if ((wk.array () * (corr * wk).array () - b.array ()).abs ().maxCoeff () < tol)
64+ break ;
65+ }
66+ vector_t w = wk.array () / vol.array ();
67+ return (w / w.sum ()).matrix ();
68+ }
69+
70+
71+ vector_t rpp_design (c_matrix_t & cov,
72+ c_vector_t & b,
73+ const double tol = 1E-4 ,
74+ const unsigned int maxiter = 100 ,
75+ const char * method = " spinu"
76+ ){
77+ if (strcmp (method, " spinu" ))
78+ return risk_parity_portfolio_ccd_spinu (cov, b, tol, maxiter);
79+ else
80+ return risk_parity_portfolio_ccd_choi (cov, b, tol, maxiter);
81+ }
82+
3783namespace py = pybind11;
3884
3985
4086PYBIND11_MODULE (vanilla, m) {
4187 m.doc () = " design of risk parity portfolios" ;
42- m.def (" design" , &risk_parity_portfolio_ccd_spinu,
43- py::arg (" Sigma" ), py::arg (" b" ),
44- py::arg (" tol" ) = 1E-8 , py::arg (" maxiter" ) = 50 ,
88+ m.def (" design" , &rpp_design,
89+ py::arg (" Sigma" ),
90+ py::arg (" b" ),
91+ py::arg (" tol" ) = 1E-4 ,
92+ py::arg (" maxiter" ) = 50 ,
93+ py::arg (" method" ) = " spinu" ,
4594 R"pbdoc(
4695 A function to design vanilla risk parity (budgeting) portfolios.
4796
@@ -63,6 +112,9 @@ PYBIND11_MODULE(vanilla, m) {
63112 maxiter : int
64113 maximum number of iterations. Default value is 50
65114
115+ method : str
116+ which method to use. Available: "spinu" and "choi"
117+
66118
67119 Example
68120 -------
0 commit comments