Skip to content

Commit aea8609

Browse files
committed
add (Choi 2022) method
1 parent 4ae2922 commit aea8609

File tree

2 files changed

+74
-19
lines changed

2 files changed

+74
-19
lines changed
Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import riskparityportfolio as rpp
22
import numpy as np
3+
import pytest
34

4-
5-
def test():
6-
n = 1000
5+
@pytest.mark.parametrize("method", ["choi", "spinu"])
6+
def test(method):
7+
n = 100
78
t = 1000 * n
89
x = np.random.normal(size=t).reshape((n, -1))
9-
corr = x @ x.T / t
10-
b = np.ones(len(corr)) / len(corr)
11-
w = rpp.vanilla.design(corr, b)
12-
rc = w @ (corr * w)
10+
cov = np.cov(x)
11+
print(cov.shape)
12+
b = np.ones(len(cov)) / len(cov)
13+
w = rpp.vanilla.design(cov, b, maxiter=1000, method=method)
14+
print(w)
15+
rc = w @ (cov * w)
1316
rc = rc / np.sum(rc)
1417
# assert that the portfolio respect the budget constraint
1518
np.testing.assert_almost_equal(np.sum(w), 1)
16-
# assert that the portfolio repect the no-shortselling constraint
17-
np.testing.assert_equal(all(w > 0), True)
19+
# assert that the portfolio respect the no-shortselling constraint
20+
np.testing.assert_equal(all(w >= 0), True)
1821
# assert that the desired risk contributions are attained
19-
np.testing.assert_allclose(rc, b, rtol=1e-4)
22+
np.testing.assert_allclose(rc, b, atol=1/(10*n))

riskparityportfolio/vanilla.cpp

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1+
#include <cstring>
2+
#include <Eigen/Dense>
13
#include <pybind11/pybind11.h>
24
#include <pybind11/eigen.h>
35

46
using namespace Eigen;
57
using 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+
3783
namespace py = pybind11;
3884

3985

4086
PYBIND11_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

Comments
 (0)