skfolio 0.2.3__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- skfolio/datasets/__init__.py +2 -0
- skfolio/datasets/_base.py +51 -0
- skfolio/distance/_distance.py +15 -4
- skfolio/model_selection/_combinatorial.py +2 -2
- skfolio/model_selection/_validation.py +70 -15
- skfolio/model_selection/_walk_forward.py +3 -3
- skfolio/moments/__init__.py +2 -0
- skfolio/moments/covariance/__init__.py +11 -11
- skfolio/moments/covariance/_base.py +10 -9
- skfolio/moments/covariance/_denoise_covariance.py +181 -0
- skfolio/moments/covariance/_detone_covariance.py +158 -0
- skfolio/moments/covariance/_empirical_covariance.py +100 -0
- skfolio/moments/covariance/_ew_covariance.py +109 -0
- skfolio/moments/covariance/_gerber_covariance.py +157 -0
- skfolio/moments/covariance/_graphical_lasso_cv.py +194 -0
- skfolio/moments/covariance/_implied_covariance.py +462 -0
- skfolio/moments/covariance/_ledoit_wolf.py +140 -0
- skfolio/moments/covariance/_oas.py +115 -0
- skfolio/moments/covariance/_shrunk_covariance.py +104 -0
- skfolio/moments/expected_returns/__init__.py +4 -7
- skfolio/moments/expected_returns/_empirical_mu.py +63 -0
- skfolio/moments/expected_returns/_equilibrium_mu.py +124 -0
- skfolio/moments/expected_returns/_ew_mu.py +69 -0
- skfolio/moments/expected_returns/{_expected_returns.py → _shrunk_mu.py} +22 -200
- skfolio/optimization/cluster/_nco.py +46 -8
- skfolio/optimization/cluster/hierarchical/_base.py +21 -1
- skfolio/optimization/cluster/hierarchical/_herc.py +18 -4
- skfolio/optimization/cluster/hierarchical/_hrp.py +13 -4
- skfolio/optimization/convex/_base.py +10 -1
- skfolio/optimization/convex/_distributionally_robust.py +12 -2
- skfolio/optimization/convex/_maximum_diversification.py +9 -2
- skfolio/optimization/convex/_mean_risk.py +33 -6
- skfolio/optimization/convex/_risk_budgeting.py +5 -2
- skfolio/optimization/ensemble/_stacking.py +32 -9
- skfolio/optimization/naive/_naive.py +20 -2
- skfolio/population/_population.py +2 -0
- skfolio/prior/_base.py +1 -1
- skfolio/prior/_black_litterman.py +20 -2
- skfolio/prior/_empirical.py +38 -5
- skfolio/prior/_factor_model.py +44 -7
- skfolio/uncertainty_set/_base.py +30 -9
- skfolio/uncertainty_set/_bootstrap.py +26 -10
- skfolio/uncertainty_set/_empirical.py +25 -10
- skfolio/utils/stats.py +24 -3
- skfolio/utils/tools.py +213 -79
- {skfolio-0.2.3.dist-info → skfolio-0.3.1.dist-info}/METADATA +3 -2
- skfolio-0.3.1.dist-info/RECORD +91 -0
- {skfolio-0.2.3.dist-info → skfolio-0.3.1.dist-info}/WHEEL +1 -1
- skfolio/moments/covariance/_covariance.py +0 -1114
- skfolio-0.2.3.dist-info/RECORD +0 -79
- {skfolio-0.2.3.dist-info → skfolio-0.3.1.dist-info}/LICENSE +0 -0
- {skfolio-0.2.3.dist-info → skfolio-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
"""LedoitWolf Covariance Estimators."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy.typing as npt
|
11
|
+
import sklearn.covariance as skc
|
12
|
+
|
13
|
+
from skfolio.moments.covariance._base import BaseCovariance
|
14
|
+
|
15
|
+
|
16
|
+
class LedoitWolf(BaseCovariance, skc.LedoitWolf):
|
17
|
+
"""LedoitWolf Covariance Estimator.
|
18
|
+
|
19
|
+
Ledoit-Wolf is a particular form of shrinkage, where the shrinkage
|
20
|
+
coefficient is computed using O. Ledoit and M. Wolf's formula as
|
21
|
+
described in [1]_.
|
22
|
+
|
23
|
+
Read more in `scikit-learn
|
24
|
+
<https://scikit-learn.org/stable/modules/generated/sklearn.covariance.ShrunkCovariance.html>`_.
|
25
|
+
|
26
|
+
Parameters
|
27
|
+
----------
|
28
|
+
store_precision : bool, default=True
|
29
|
+
Specify if the estimated precision is stored.
|
30
|
+
|
31
|
+
assume_centered : bool, default=False
|
32
|
+
If True, data will not be centered before computation.
|
33
|
+
Useful when working with data whose mean is almost, but not exactly
|
34
|
+
zero.
|
35
|
+
If False (default), data will be centered before computation.
|
36
|
+
|
37
|
+
block_size : int, default=1000
|
38
|
+
Size of blocks into which the covariance matrix will be split
|
39
|
+
during its Ledoit-Wolf estimation. This is purely a memory
|
40
|
+
optimization and does not affect results.
|
41
|
+
|
42
|
+
nearest : bool, default=True
|
43
|
+
If this is set to True, the covariance is replaced by the nearest covariance
|
44
|
+
matrix that is positive definite and with a Cholesky decomposition than can be
|
45
|
+
computed. The variance is left unchanged.
|
46
|
+
A covariance matrix that is not positive definite often occurs in high
|
47
|
+
dimensional problems. It can be due to multicollinearity, floating-point
|
48
|
+
inaccuracies, or when the number of observations is smaller than the number of
|
49
|
+
assets. For more details, see :func:`~skfolio.utils.stats.cov_nearest`.
|
50
|
+
The default is `True`.
|
51
|
+
|
52
|
+
higham : bool, default=False
|
53
|
+
If this is set to True, the Higham & Nick (2002) algorithm is used to find the
|
54
|
+
nearest PD covariance, otherwise the eigenvalues are clipped to a threshold
|
55
|
+
above zeros (1e-13). The default is `False` and use the clipping method as the
|
56
|
+
Higham & Nick algorithm can be slow for large datasets.
|
57
|
+
|
58
|
+
higham_max_iteration : int, default=100
|
59
|
+
Maximum number of iteration of the Higham & Nick (2002) algorithm.
|
60
|
+
The default value is `100`.
|
61
|
+
|
62
|
+
Attributes
|
63
|
+
----------
|
64
|
+
covariance_ : ndarray of shape (n_assets, n_assets)
|
65
|
+
Estimated covariance.
|
66
|
+
|
67
|
+
location_ : ndarray of shape (n_assets,)
|
68
|
+
Estimated location, i.e. the estimated mean.
|
69
|
+
|
70
|
+
precision_ : ndarray of shape (n_assets, n_assets)
|
71
|
+
Estimated pseudo inverse matrix.
|
72
|
+
(stored only if store_precision is True)
|
73
|
+
|
74
|
+
shrinkage_ : float
|
75
|
+
Coefficient in the convex combination used for the computation
|
76
|
+
of the shrunk estimate. Range is [0, 1].
|
77
|
+
|
78
|
+
n_features_in_ : int
|
79
|
+
Number of assets seen during `fit`.
|
80
|
+
|
81
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
82
|
+
Names of features seen during `fit`. Defined only when `X`
|
83
|
+
has feature names that are all strings.
|
84
|
+
|
85
|
+
Notes
|
86
|
+
-----
|
87
|
+
The regularised covariance is:
|
88
|
+
|
89
|
+
(1 - shrinkage) * cov + shrinkage * mu * np.identity(n_features)
|
90
|
+
|
91
|
+
where mu = trace(cov) / n_features
|
92
|
+
and shrinkage is given by the Ledoit and Wolf formula (see References)
|
93
|
+
|
94
|
+
References
|
95
|
+
----------
|
96
|
+
.. [1] "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices".
|
97
|
+
Ledoit and Wolf, Journal of Multivariate Analysis, Volume 88, Issue 2.
|
98
|
+
February 2004, pages 365-41.
|
99
|
+
"""
|
100
|
+
|
101
|
+
def __init__(
|
102
|
+
self,
|
103
|
+
store_precision=True,
|
104
|
+
assume_centered=False,
|
105
|
+
block_size=1000,
|
106
|
+
nearest: bool = True,
|
107
|
+
higham: bool = False,
|
108
|
+
higham_max_iteration: int = 100,
|
109
|
+
):
|
110
|
+
super().__init__(
|
111
|
+
nearest=nearest,
|
112
|
+
higham=higham,
|
113
|
+
higham_max_iteration=higham_max_iteration,
|
114
|
+
)
|
115
|
+
skc.LedoitWolf.__init__(
|
116
|
+
self,
|
117
|
+
store_precision=store_precision,
|
118
|
+
assume_centered=assume_centered,
|
119
|
+
block_size=block_size,
|
120
|
+
)
|
121
|
+
|
122
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "LedoitWolf":
|
123
|
+
"""Fit the Ledoit-Wolf shrunk covariance model to X.
|
124
|
+
|
125
|
+
Parameters
|
126
|
+
----------
|
127
|
+
X : array-like of shape (n_observations, n_assets)
|
128
|
+
Price returns of the assets.
|
129
|
+
|
130
|
+
y : Ignored
|
131
|
+
Not used, present for API consistency by convention.
|
132
|
+
|
133
|
+
Returns
|
134
|
+
-------
|
135
|
+
self : LedoitWolf
|
136
|
+
Fitted estimator.
|
137
|
+
"""
|
138
|
+
skc.LedoitWolf.fit(self, X)
|
139
|
+
self._set_covariance(self.covariance_)
|
140
|
+
return self
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""Oracle Approximating Shrinkage Covariance Estimators."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy.typing as npt
|
11
|
+
import sklearn.covariance as skc
|
12
|
+
|
13
|
+
from skfolio.moments.covariance._base import BaseCovariance
|
14
|
+
|
15
|
+
|
16
|
+
class OAS(BaseCovariance, skc.OAS):
|
17
|
+
"""Oracle Approximating Shrinkage Estimator as proposed in [1]_.
|
18
|
+
|
19
|
+
Read more in `scikit-learn
|
20
|
+
<https://scikit-learn.org/stable/modules/generated/sklearn.covariance.ShrunkCovariance.html>`_.
|
21
|
+
|
22
|
+
Parameters
|
23
|
+
----------
|
24
|
+
store_precision : bool, default=True
|
25
|
+
Specify if the estimated precision is stored.
|
26
|
+
|
27
|
+
assume_centered : bool, default=False
|
28
|
+
If True, data will not be centered before computation.
|
29
|
+
Useful when working with data whose mean is almost, but not exactly
|
30
|
+
zero.
|
31
|
+
If False (default), data will be centered before computation.
|
32
|
+
|
33
|
+
Attributes
|
34
|
+
----------
|
35
|
+
covariance_ : ndarray of shape (n_assets, n_assets)
|
36
|
+
Estimated covariance.
|
37
|
+
|
38
|
+
location_ : ndarray of shape (n_assets,)
|
39
|
+
Estimated location, i.e. the estimated mean.
|
40
|
+
|
41
|
+
precision_ : ndarray of shape (n_assets, n_assets)
|
42
|
+
Estimated pseudo inverse matrix.
|
43
|
+
(stored only if store_precision is True)
|
44
|
+
|
45
|
+
shrinkage_ : float
|
46
|
+
Coefficient in the convex combination used for the computation
|
47
|
+
of the shrunk estimate. Range is [0, 1].
|
48
|
+
|
49
|
+
n_features_in_ : int
|
50
|
+
Number of assets seen during `fit`.
|
51
|
+
|
52
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
53
|
+
Names of features seen during `fit`. Defined only when `X`
|
54
|
+
has feature names that are all strings.
|
55
|
+
|
56
|
+
Notes
|
57
|
+
-----
|
58
|
+
The regularised covariance is:
|
59
|
+
|
60
|
+
(1 - shrinkage) * cov + shrinkage * mu * np.identity(n_features),
|
61
|
+
|
62
|
+
where mu = trace(cov) / n_features and shrinkage is given by the OAS formula
|
63
|
+
(see [1]_).
|
64
|
+
|
65
|
+
The shrinkage formulation implemented here differs from Eq. 23 in [1]_. In
|
66
|
+
the original article, formula (23) states that 2/p (p being the number of
|
67
|
+
features) is multiplied by Trace(cov*cov) in both the numerator and
|
68
|
+
denominator, but this operation is omitted because for a large p, the value
|
69
|
+
of 2/p is so small that it doesn't affect the value of the estimator.
|
70
|
+
|
71
|
+
References
|
72
|
+
----------
|
73
|
+
.. [1] "Shrinkage algorithms for MMSE covariance estimation".
|
74
|
+
Chen, Y., Wiesel, A., Eldar, Y. C., & Hero, A. O.
|
75
|
+
IEEE Transactions on Signal Processing, 58(10), 5016-5029, 2010.
|
76
|
+
"""
|
77
|
+
|
78
|
+
def __init__(
|
79
|
+
self,
|
80
|
+
store_precision=True,
|
81
|
+
assume_centered=False,
|
82
|
+
nearest: bool = True,
|
83
|
+
higham: bool = False,
|
84
|
+
higham_max_iteration: int = 100,
|
85
|
+
):
|
86
|
+
super().__init__(
|
87
|
+
nearest=nearest,
|
88
|
+
higham=higham,
|
89
|
+
higham_max_iteration=higham_max_iteration,
|
90
|
+
)
|
91
|
+
skc.OAS.__init__(
|
92
|
+
self,
|
93
|
+
store_precision=store_precision,
|
94
|
+
assume_centered=assume_centered,
|
95
|
+
)
|
96
|
+
|
97
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "OAS":
|
98
|
+
"""Fit the Oracle Approximating Shrinkage covariance model to X.
|
99
|
+
|
100
|
+
Parameters
|
101
|
+
----------
|
102
|
+
X : array-like of shape (n_observations, n_assets)
|
103
|
+
Price returns of the assets.
|
104
|
+
|
105
|
+
y : Ignored
|
106
|
+
Not used, present for API consistency by convention.
|
107
|
+
|
108
|
+
Returns
|
109
|
+
-------
|
110
|
+
self : OAS
|
111
|
+
Fitted estimator.
|
112
|
+
"""
|
113
|
+
skc.OAS.fit(self, X)
|
114
|
+
self._set_covariance(self.covariance_)
|
115
|
+
return self
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""Shrunk Covariance Estimators."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy.typing as npt
|
11
|
+
import sklearn.covariance as skc
|
12
|
+
|
13
|
+
from skfolio.moments.covariance._base import BaseCovariance
|
14
|
+
|
15
|
+
|
16
|
+
class ShrunkCovariance(BaseCovariance, skc.ShrunkCovariance):
|
17
|
+
"""Covariance estimator with shrinkage.
|
18
|
+
|
19
|
+
Read more in `scikit-learn
|
20
|
+
<https://scikit-learn.org/stable/modules/generated/sklearn.covariance.ShrunkCovariance.html>`_.
|
21
|
+
|
22
|
+
Parameters
|
23
|
+
----------
|
24
|
+
store_precision : bool, default=True
|
25
|
+
Specify if the estimated precision is stored.
|
26
|
+
|
27
|
+
assume_centered : bool, default=False
|
28
|
+
If True, data will not be centered before computation.
|
29
|
+
Useful when working with data whose mean is almost, but not exactly
|
30
|
+
zero.
|
31
|
+
If False (default), data will be centered before computation.
|
32
|
+
|
33
|
+
shrinkage : float, default=0.1
|
34
|
+
Coefficient in the convex combination used for the computation
|
35
|
+
of the shrunk estimate. Range is [0, 1].
|
36
|
+
|
37
|
+
Attributes
|
38
|
+
----------
|
39
|
+
covariance_ : ndarray of shape (n_assets, n_assets)
|
40
|
+
Estimated covariance.
|
41
|
+
|
42
|
+
location_ : ndarray of shape (n_assets,)
|
43
|
+
Estimated location, i.e. the estimated mean.
|
44
|
+
|
45
|
+
precision_ : ndarray of shape (n_assets, n_assets)
|
46
|
+
Estimated pseudo inverse matrix.
|
47
|
+
(stored only if store_precision is True)
|
48
|
+
|
49
|
+
n_features_in_ : int
|
50
|
+
Number of assets seen during `fit`.
|
51
|
+
|
52
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
53
|
+
Names of features seen during `fit`. Defined only when `X`
|
54
|
+
has feature names that are all strings.
|
55
|
+
|
56
|
+
Notes
|
57
|
+
-----
|
58
|
+
The regularized covariance is given by:
|
59
|
+
|
60
|
+
(1 - shrinkage) * cov + shrinkage * mu * np.identity(n_features)
|
61
|
+
|
62
|
+
where mu = trace(cov) / n_features
|
63
|
+
"""
|
64
|
+
|
65
|
+
def __init__(
|
66
|
+
self,
|
67
|
+
store_precision=True,
|
68
|
+
assume_centered=False,
|
69
|
+
shrinkage=0.1,
|
70
|
+
nearest: bool = True,
|
71
|
+
higham: bool = False,
|
72
|
+
higham_max_iteration: int = 100,
|
73
|
+
):
|
74
|
+
super().__init__(
|
75
|
+
nearest=nearest,
|
76
|
+
higham=higham,
|
77
|
+
higham_max_iteration=higham_max_iteration,
|
78
|
+
)
|
79
|
+
skc.ShrunkCovariance.__init__(
|
80
|
+
self,
|
81
|
+
store_precision=store_precision,
|
82
|
+
assume_centered=assume_centered,
|
83
|
+
shrinkage=shrinkage,
|
84
|
+
)
|
85
|
+
|
86
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "ShrunkCovariance":
|
87
|
+
"""Fit the shrunk covariance model to X.
|
88
|
+
|
89
|
+
Parameters
|
90
|
+
----------
|
91
|
+
X : array-like of shape (n_observations, n_assets)
|
92
|
+
Price returns of the assets.
|
93
|
+
|
94
|
+
y : Ignored
|
95
|
+
Not used, present for API consistency by convention.
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
self : ShrunkCovariance
|
100
|
+
Fitted estimator.
|
101
|
+
"""
|
102
|
+
skc.ShrunkCovariance.fit(self, X)
|
103
|
+
self._set_covariance(self.covariance_)
|
104
|
+
return self
|
@@ -3,13 +3,10 @@
|
|
3
3
|
from skfolio.moments.expected_returns._base import (
|
4
4
|
BaseMu,
|
5
5
|
)
|
6
|
-
from skfolio.moments.expected_returns.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
ShrunkMu,
|
11
|
-
ShrunkMuMethods,
|
12
|
-
)
|
6
|
+
from skfolio.moments.expected_returns._empirical_mu import EmpiricalMu
|
7
|
+
from skfolio.moments.expected_returns._equilibrium_mu import EquilibriumMu
|
8
|
+
from skfolio.moments.expected_returns._ew_mu import EWMu
|
9
|
+
from skfolio.moments.expected_returns._shrunk_mu import ShrunkMu, ShrunkMuMethods
|
13
10
|
|
14
11
|
__all__ = [
|
15
12
|
"BaseMu",
|
@@ -0,0 +1,63 @@
|
|
1
|
+
"""Empirical Expected Returns (Mu) Estimator."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import numpy.typing as npt
|
12
|
+
|
13
|
+
from skfolio.moments.expected_returns._base import BaseMu
|
14
|
+
|
15
|
+
|
16
|
+
class EmpiricalMu(BaseMu):
|
17
|
+
"""Empirical Expected Returns (Mu) estimator.
|
18
|
+
|
19
|
+
Estimates the expected returns with the historical mean.
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
window_size : int, optional
|
24
|
+
Window size. The model is fitted on the last `window_size` observations.
|
25
|
+
The default (`None`) is to use all the data.
|
26
|
+
|
27
|
+
Attributes
|
28
|
+
----------
|
29
|
+
mu_ : ndarray of shape (n_assets,)
|
30
|
+
Estimated expected returns of the assets.
|
31
|
+
|
32
|
+
n_features_in_ : int
|
33
|
+
Number of assets seen during `fit`.
|
34
|
+
|
35
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
36
|
+
Names of assets seen during `fit`. Defined only when `X`
|
37
|
+
has assets names that are all strings.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self, window_size: int | None = None):
|
41
|
+
self.window_size = window_size
|
42
|
+
|
43
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "EmpiricalMu":
|
44
|
+
"""Fit the Mu Empirical estimator model.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
X : array-like of shape (n_observations, n_assets)
|
49
|
+
Price returns of the assets.
|
50
|
+
|
51
|
+
y : Ignored
|
52
|
+
Not used, present for API consistency by convention.
|
53
|
+
|
54
|
+
Returns
|
55
|
+
-------
|
56
|
+
self : EmpiricalMu
|
57
|
+
Fitted estimator.
|
58
|
+
"""
|
59
|
+
X = self._validate_data(X)
|
60
|
+
if self.window_size is not None:
|
61
|
+
X = X[-self.window_size :]
|
62
|
+
self.mu_ = np.mean(X, axis=0)
|
63
|
+
return self
|
@@ -0,0 +1,124 @@
|
|
1
|
+
"""Equilibrium Expected Returns (Mu) Estimators."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
import numpy.typing as npt
|
12
|
+
import sklearn.utils.metadata_routing as skm
|
13
|
+
|
14
|
+
from skfolio.moments.covariance import BaseCovariance, EmpiricalCovariance
|
15
|
+
from skfolio.moments.expected_returns._base import BaseMu
|
16
|
+
from skfolio.utils.tools import check_estimator
|
17
|
+
|
18
|
+
|
19
|
+
class EquilibriumMu(BaseMu):
|
20
|
+
r"""Equilibrium Expected Returns (Mu) estimator.
|
21
|
+
|
22
|
+
The Equilibrium is defined as:
|
23
|
+
|
24
|
+
.. math:: risk\_aversion \times \Sigma \cdot w^T
|
25
|
+
|
26
|
+
For Market Cap Equilibrium, the weights are the assets Market Caps.
|
27
|
+
For Equal-weighted Equilibrium, the weights are equal-weighted (1/N).
|
28
|
+
|
29
|
+
Parameters
|
30
|
+
----------
|
31
|
+
risk_aversion : float, default=1.0
|
32
|
+
Risk aversion factor.
|
33
|
+
The default value is `1.0`.
|
34
|
+
|
35
|
+
weights : array-like of shape (n_assets,), optional
|
36
|
+
Asset weights used to compute the Expected Return Equilibrium.
|
37
|
+
The default is to use the equal-weighted equilibrium (1/N).
|
38
|
+
For a Market Cap weighted equilibrium, you must provide the asset Market Caps.
|
39
|
+
|
40
|
+
covariance_estimator : BaseCovariance, optional
|
41
|
+
:ref:`Covariance estimator <covariance_estimator>` used to estimate the
|
42
|
+
covariance in the equilibrium formula.
|
43
|
+
The default (`None`) is to use :class:`~skfolio.moments.EmpiricalCovariance`.
|
44
|
+
|
45
|
+
Attributes
|
46
|
+
----------
|
47
|
+
mu_ : ndarray of shape (n_assets,)
|
48
|
+
Estimated expected returns of the assets.
|
49
|
+
|
50
|
+
covariance_estimator_ : BaseCovariance
|
51
|
+
Fitted `covariance_estimator`.
|
52
|
+
|
53
|
+
n_features_in_ : int
|
54
|
+
Number of assets seen during `fit`.
|
55
|
+
|
56
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
57
|
+
Names of assets seen during `fit`. Defined only when `X`
|
58
|
+
has assets names that are all strings.
|
59
|
+
"""
|
60
|
+
|
61
|
+
covariance_estimator_: BaseCovariance
|
62
|
+
|
63
|
+
def __init__(
|
64
|
+
self,
|
65
|
+
risk_aversion: float = 1,
|
66
|
+
weights: np.ndarray | None = None,
|
67
|
+
covariance_estimator: BaseCovariance | None = None,
|
68
|
+
):
|
69
|
+
self.risk_aversion = risk_aversion
|
70
|
+
self.weights = weights
|
71
|
+
self.covariance_estimator = covariance_estimator
|
72
|
+
|
73
|
+
def get_metadata_routing(self):
|
74
|
+
# noinspection PyTypeChecker
|
75
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
76
|
+
covariance_estimator=self.covariance_estimator,
|
77
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
78
|
+
)
|
79
|
+
return router
|
80
|
+
|
81
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params) -> "EquilibriumMu":
|
82
|
+
"""Fit the EquilibriumMu estimator model.
|
83
|
+
|
84
|
+
Parameters
|
85
|
+
----------
|
86
|
+
X : array-like of shape (n_observations, n_assets)
|
87
|
+
Price returns of the assets.
|
88
|
+
|
89
|
+
y : Ignored
|
90
|
+
Not used, present for API consistency by convention.
|
91
|
+
|
92
|
+
**fit_params : dict
|
93
|
+
Parameters to pass to the underlying estimators.
|
94
|
+
Only available if `enable_metadata_routing=True`, which can be
|
95
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
96
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
97
|
+
more details.
|
98
|
+
|
99
|
+
Returns
|
100
|
+
-------
|
101
|
+
self : EquilibriumMu
|
102
|
+
Fitted estimator.
|
103
|
+
"""
|
104
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
105
|
+
|
106
|
+
# fitting estimators
|
107
|
+
self.covariance_estimator_ = check_estimator(
|
108
|
+
self.covariance_estimator,
|
109
|
+
default=EmpiricalCovariance(),
|
110
|
+
check_type=BaseCovariance,
|
111
|
+
)
|
112
|
+
# noinspection PyArgumentList
|
113
|
+
self.covariance_estimator_.fit(X, y, **routed_params.covariance_estimator.fit)
|
114
|
+
|
115
|
+
# we validate and convert to numpy after all models have been fitted to keep
|
116
|
+
# features names information.
|
117
|
+
X = self._validate_data(X)
|
118
|
+
n_assets = X.shape[1]
|
119
|
+
if self.weights is None:
|
120
|
+
weights = np.ones(n_assets) / n_assets
|
121
|
+
else:
|
122
|
+
weights = np.asarray(self.weights)
|
123
|
+
self.mu_ = self.risk_aversion * self.covariance_estimator_.covariance_ @ weights
|
124
|
+
return self
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"""Exponentially Weighted Expected Returns (Mu) Estimators."""
|
2
|
+
|
3
|
+
# Copyright (c) 2023
|
4
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
+
# License: BSD 3 clause
|
6
|
+
# Implementation derived from:
|
7
|
+
# scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
|
8
|
+
# Grisel Licensed under BSD 3 clause.
|
9
|
+
|
10
|
+
import numpy.typing as npt
|
11
|
+
import pandas as pd
|
12
|
+
|
13
|
+
from skfolio.moments.expected_returns._base import BaseMu
|
14
|
+
|
15
|
+
|
16
|
+
class EWMu(BaseMu):
|
17
|
+
r"""Exponentially Weighted Expected Returns (Mu) estimator.
|
18
|
+
|
19
|
+
Estimates the expected returns with the exponentially weighted mean (EWM).
|
20
|
+
|
21
|
+
Parameters
|
22
|
+
----------
|
23
|
+
window_size : int, optional
|
24
|
+
Window size. The model is fitted on the last `window_size` observations.
|
25
|
+
The default (`None`) is to use all the data.
|
26
|
+
|
27
|
+
alpha : float, default=0.2
|
28
|
+
Exponential smoothing factor. The default value is `0.2`.
|
29
|
+
|
30
|
+
:math:`0 < \alpha \leq 1`.
|
31
|
+
|
32
|
+
Attributes
|
33
|
+
----------
|
34
|
+
mu_ : ndarray of shape (n_assets,)
|
35
|
+
Estimated expected returns of the assets.
|
36
|
+
|
37
|
+
n_features_in_ : int
|
38
|
+
Number of assets seen during `fit`.
|
39
|
+
|
40
|
+
feature_names_in_ : ndarray of shape (`n_features_in_`,)
|
41
|
+
Names of assets seen during `fit`. Defined only when `X`
|
42
|
+
has assets names that are all strings.
|
43
|
+
"""
|
44
|
+
|
45
|
+
def __init__(self, window_size: int | None = None, alpha: float = 0.2):
|
46
|
+
self.window_size = window_size
|
47
|
+
self.alpha = alpha
|
48
|
+
|
49
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "EWMu":
|
50
|
+
"""Fit the EWMu estimator model.
|
51
|
+
|
52
|
+
Parameters
|
53
|
+
----------
|
54
|
+
X : array-like of shape (n_observations, n_assets)
|
55
|
+
Price returns of the assets.
|
56
|
+
|
57
|
+
y : Ignored
|
58
|
+
Not used, present for API consistency by convention.
|
59
|
+
|
60
|
+
Returns
|
61
|
+
-------
|
62
|
+
self : EWMu
|
63
|
+
Fitted estimator.
|
64
|
+
"""
|
65
|
+
X = self._validate_data(X)
|
66
|
+
if self.window_size is not None:
|
67
|
+
X = X[-self.window_size :]
|
68
|
+
self.mu_ = pd.DataFrame(X).ewm(alpha=self.alpha).mean().iloc[-1, :].to_numpy()
|
69
|
+
return self
|