skfolio 0.0.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/__init__.py +29 -0
- skfolio/cluster/__init__.py +8 -0
- skfolio/cluster/_hierarchical.py +387 -0
- skfolio/datasets/__init__.py +20 -0
- skfolio/datasets/_base.py +389 -0
- skfolio/datasets/data/__init__.py +0 -0
- skfolio/datasets/data/factors_dataset.csv.gz +0 -0
- skfolio/datasets/data/sp500_dataset.csv.gz +0 -0
- skfolio/datasets/data/sp500_index.csv.gz +0 -0
- skfolio/distance/__init__.py +26 -0
- skfolio/distance/_base.py +55 -0
- skfolio/distance/_distance.py +574 -0
- skfolio/exceptions.py +30 -0
- skfolio/measures/__init__.py +76 -0
- skfolio/measures/_enums.py +355 -0
- skfolio/measures/_measures.py +607 -0
- skfolio/metrics/__init__.py +3 -0
- skfolio/metrics/_scorer.py +121 -0
- skfolio/model_selection/__init__.py +18 -0
- skfolio/model_selection/_combinatorial.py +407 -0
- skfolio/model_selection/_validation.py +194 -0
- skfolio/model_selection/_walk_forward.py +221 -0
- skfolio/moments/__init__.py +41 -0
- skfolio/moments/covariance/__init__.py +29 -0
- skfolio/moments/covariance/_base.py +101 -0
- skfolio/moments/covariance/_covariance.py +1108 -0
- skfolio/moments/expected_returns/__init__.py +21 -0
- skfolio/moments/expected_returns/_base.py +31 -0
- skfolio/moments/expected_returns/_expected_returns.py +415 -0
- skfolio/optimization/__init__.py +36 -0
- skfolio/optimization/_base.py +147 -0
- skfolio/optimization/cluster/__init__.py +13 -0
- skfolio/optimization/cluster/_nco.py +348 -0
- skfolio/optimization/cluster/hierarchical/__init__.py +13 -0
- skfolio/optimization/cluster/hierarchical/_base.py +440 -0
- skfolio/optimization/cluster/hierarchical/_herc.py +406 -0
- skfolio/optimization/cluster/hierarchical/_hrp.py +368 -0
- skfolio/optimization/convex/__init__.py +16 -0
- skfolio/optimization/convex/_base.py +1944 -0
- skfolio/optimization/convex/_distributionally_robust.py +392 -0
- skfolio/optimization/convex/_maximum_diversification.py +417 -0
- skfolio/optimization/convex/_mean_risk.py +974 -0
- skfolio/optimization/convex/_risk_budgeting.py +560 -0
- skfolio/optimization/ensemble/__init__.py +6 -0
- skfolio/optimization/ensemble/_base.py +87 -0
- skfolio/optimization/ensemble/_stacking.py +326 -0
- skfolio/optimization/naive/__init__.py +3 -0
- skfolio/optimization/naive/_naive.py +173 -0
- skfolio/population/__init__.py +3 -0
- skfolio/population/_population.py +883 -0
- skfolio/portfolio/__init__.py +13 -0
- skfolio/portfolio/_base.py +1096 -0
- skfolio/portfolio/_multi_period_portfolio.py +610 -0
- skfolio/portfolio/_portfolio.py +842 -0
- skfolio/pre_selection/__init__.py +7 -0
- skfolio/pre_selection/_pre_selection.py +342 -0
- skfolio/preprocessing/__init__.py +3 -0
- skfolio/preprocessing/_returns.py +114 -0
- skfolio/prior/__init__.py +18 -0
- skfolio/prior/_base.py +63 -0
- skfolio/prior/_black_litterman.py +238 -0
- skfolio/prior/_empirical.py +163 -0
- skfolio/prior/_factor_model.py +268 -0
- skfolio/typing.py +50 -0
- skfolio/uncertainty_set/__init__.py +23 -0
- skfolio/uncertainty_set/_base.py +108 -0
- skfolio/uncertainty_set/_bootstrap.py +281 -0
- skfolio/uncertainty_set/_empirical.py +237 -0
- skfolio/utils/__init__.py +0 -0
- skfolio/utils/bootstrap.py +115 -0
- skfolio/utils/equations.py +350 -0
- skfolio/utils/sorting.py +117 -0
- skfolio/utils/stats.py +466 -0
- skfolio/utils/tools.py +567 -0
- skfolio-0.0.1.dist-info/LICENSE +29 -0
- skfolio-0.0.1.dist-info/METADATA +568 -0
- skfolio-0.0.1.dist-info/RECORD +79 -0
- skfolio-0.0.1.dist-info/WHEEL +5 -0
- skfolio-0.0.1.dist-info/top_level.txt +1 -0
skfolio/typing.py
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
"""Custom typing module."""
|
2
|
+
|
3
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
4
|
+
# License: BSD 3 clause
|
5
|
+
|
6
|
+
from collections.abc import Callable
|
7
|
+
|
8
|
+
import cvxpy as cp
|
9
|
+
import numpy as np
|
10
|
+
import numpy.typing as npt
|
11
|
+
import plotly.graph_objects as go
|
12
|
+
|
13
|
+
from skfolio.measures import ExtraRiskMeasure, PerfMeasure, RatioMeasure, RiskMeasure
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"Groups",
|
17
|
+
"Inequality",
|
18
|
+
"LinearConstraints",
|
19
|
+
"MultiInput",
|
20
|
+
"Target",
|
21
|
+
"ParametersValues",
|
22
|
+
"Factor",
|
23
|
+
"Result",
|
24
|
+
"RiskResult",
|
25
|
+
"ExpressionFunction",
|
26
|
+
"Measure",
|
27
|
+
"CvxMeasure",
|
28
|
+
"Names",
|
29
|
+
"Tags",
|
30
|
+
]
|
31
|
+
|
32
|
+
Measure = PerfMeasure | RiskMeasure | ExtraRiskMeasure | RatioMeasure
|
33
|
+
CvxMeasure = PerfMeasure | RiskMeasure | RatioMeasure
|
34
|
+
MultiInput = float | dict[str, float] | npt.ArrayLike
|
35
|
+
Groups = dict[str, list[str]] | np.ndarray | list[list[str]]
|
36
|
+
LinearConstraints = np.ndarray | list[str]
|
37
|
+
Inequality = np.ndarray | list
|
38
|
+
Target = float | np.ndarray
|
39
|
+
ParametersValues = list[tuple[cp.Parameter, float | np.ndarray]]
|
40
|
+
Factor = cp.Variable | cp.Constant
|
41
|
+
Result = np.ndarray | tuple[float | tuple[float, float] | np.ndarray, np.ndarray]
|
42
|
+
RiskResult = tuple[
|
43
|
+
cp.Expression | cp.Variable | cp.trace, list[cp.Expression | cp.SOC | cp.PSD]
|
44
|
+
]
|
45
|
+
ExpressionFunction = Callable[[cp.Variable, any], cp.Expression]
|
46
|
+
Figure = go.Figure
|
47
|
+
|
48
|
+
# Population
|
49
|
+
Names = str | list[str]
|
50
|
+
Tags = str | list[str]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from skfolio.uncertainty_set._base import (
|
2
|
+
BaseCovarianceUncertaintySet,
|
3
|
+
BaseMuUncertaintySet,
|
4
|
+
UncertaintySet,
|
5
|
+
)
|
6
|
+
from skfolio.uncertainty_set._bootstrap import (
|
7
|
+
BootstrapCovarianceUncertaintySet,
|
8
|
+
BootstrapMuUncertaintySet,
|
9
|
+
)
|
10
|
+
from skfolio.uncertainty_set._empirical import (
|
11
|
+
EmpiricalCovarianceUncertaintySet,
|
12
|
+
EmpiricalMuUncertaintySet,
|
13
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"UncertaintySet",
|
17
|
+
"BaseMuUncertaintySet",
|
18
|
+
"BaseCovarianceUncertaintySet",
|
19
|
+
"EmpiricalMuUncertaintySet",
|
20
|
+
"EmpiricalCovarianceUncertaintySet",
|
21
|
+
"BootstrapMuUncertaintySet",
|
22
|
+
"BootstrapCovarianceUncertaintySet",
|
23
|
+
]
|
@@ -0,0 +1,108 @@
|
|
1
|
+
"""Base Uncertainty estimator"""
|
2
|
+
|
3
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
4
|
+
# License: BSD 3 clause
|
5
|
+
|
6
|
+
from abc import ABC, abstractmethod
|
7
|
+
from dataclasses import dataclass
|
8
|
+
|
9
|
+
import numpy as np
|
10
|
+
import numpy.typing as npt
|
11
|
+
import sklearn.base as skb
|
12
|
+
|
13
|
+
|
14
|
+
# frozen=True with eq=False will lead to an id-based hashing which is needed for
|
15
|
+
# caching CVX models in Optimization without impacting performance
|
16
|
+
@dataclass(frozen=True, eq=False)
|
17
|
+
class UncertaintySet:
|
18
|
+
r"""Ellipsoidal uncertainty set dataclass.
|
19
|
+
|
20
|
+
An ellipsoidal uncertainty set is defined by its size :math:`\kappa` and
|
21
|
+
shape :math:`S`. Ellipsoidal uncertainty set can be used with both expected returns
|
22
|
+
and covariance:
|
23
|
+
|
24
|
+
Expected returns ellipsoidal uncertainty set:
|
25
|
+
|
26
|
+
.. math:: U_{\mu}=\left\{\mu\,|\left(\mu-\hat{\mu}\right)S^{-1}\left(\mu-\hat{\mu}\right)^{T}\leq\kappa^{2}\right\}
|
27
|
+
|
28
|
+
Covariance ellipsoidal uncertainty set:
|
29
|
+
|
30
|
+
.. math:: U_{\Sigma}=\left\{\Sigma\,|\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)S^{-1}\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)^{T}\leq k^{2}\,,\,\Sigma\succeq 0\right\}
|
31
|
+
|
32
|
+
Attributes
|
33
|
+
----------
|
34
|
+
k : float
|
35
|
+
Size of the ellipsoid :math:`\kappa` that defines the confidence region
|
36
|
+
|
37
|
+
sigma : ndarray of shape (n_assets)
|
38
|
+
Shape of the ellipsoid :math:`S`
|
39
|
+
"""
|
40
|
+
k: float
|
41
|
+
sigma: np.ndarray
|
42
|
+
|
43
|
+
|
44
|
+
class BaseMuUncertaintySet(skb.BaseEstimator, ABC):
|
45
|
+
"""Base class for all Mu Uncertainty Set estimators in `skfolio`.
|
46
|
+
|
47
|
+
Notes
|
48
|
+
-----
|
49
|
+
All estimators should specify all the parameters that can be set
|
50
|
+
at the class level in their ``__init__`` as explicit keyword
|
51
|
+
arguments (no ``*args`` or ``**kwargs``).
|
52
|
+
"""
|
53
|
+
|
54
|
+
uncertainty_set_: UncertaintySet
|
55
|
+
|
56
|
+
@abstractmethod
|
57
|
+
def __init__(self):
|
58
|
+
pass
|
59
|
+
|
60
|
+
@abstractmethod
|
61
|
+
def fit(self, X: npt.ArrayLike, y=None):
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
65
|
+
class BaseCovarianceUncertaintySet(skb.BaseEstimator, ABC):
|
66
|
+
"""Base class for all Covariance Uncertainty Set estimators in `skfolio`.
|
67
|
+
|
68
|
+
Notes
|
69
|
+
-----
|
70
|
+
All estimators should specify all the parameters that can be set
|
71
|
+
at the class level in their ``__init__`` as explicit keyword
|
72
|
+
arguments (no ``*args`` or ``**kwargs``).
|
73
|
+
"""
|
74
|
+
|
75
|
+
uncertainty_set_: UncertaintySet
|
76
|
+
|
77
|
+
@abstractmethod
|
78
|
+
def __init__(self):
|
79
|
+
pass
|
80
|
+
|
81
|
+
@abstractmethod
|
82
|
+
def fit(self, X: npt.ArrayLike, y=None):
|
83
|
+
pass
|
84
|
+
|
85
|
+
def _validate_X_y(self, X: npt.ArrayLike, y: npt.ArrayLike | None = None):
|
86
|
+
"""Validate X and y if provided.
|
87
|
+
|
88
|
+
Parameters
|
89
|
+
----------
|
90
|
+
X : array-like of shape (n_observations, n_assets)
|
91
|
+
Price returns of the assets.
|
92
|
+
|
93
|
+
y : array-like of shape (n_observations, n_targets), optional
|
94
|
+
Price returns of factors or a target benchmark.
|
95
|
+
The default is `None`.
|
96
|
+
|
97
|
+
Returns
|
98
|
+
-------
|
99
|
+
X : ndarray of shape (n_observations, n_assets)
|
100
|
+
Validated price returns of the assets.
|
101
|
+
y : ndarray of shape (n_observations, n_targets), optional
|
102
|
+
Validated price returns of factors or a target benchmark if provided.
|
103
|
+
"""
|
104
|
+
if y is None:
|
105
|
+
X = self._validate_data(X)
|
106
|
+
else:
|
107
|
+
X, y = self._validate_data(X, y, multi_output=True)
|
108
|
+
return X, y
|
@@ -0,0 +1,281 @@
|
|
1
|
+
"""Bootstrap Uncertainty Set estimators."""
|
2
|
+
|
3
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
4
|
+
# License: BSD 3 clause
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
import numpy.typing as npt
|
8
|
+
import scipy.stats as st
|
9
|
+
|
10
|
+
from skfolio.prior import BasePrior, EmpiricalPrior
|
11
|
+
from skfolio.uncertainty_set._base import (
|
12
|
+
BaseCovarianceUncertaintySet,
|
13
|
+
BaseMuUncertaintySet,
|
14
|
+
UncertaintySet,
|
15
|
+
)
|
16
|
+
from skfolio.utils.bootstrap import stationary_bootstrap
|
17
|
+
from skfolio.utils.tools import check_estimator
|
18
|
+
|
19
|
+
|
20
|
+
class BootstrapMuUncertaintySet(BaseMuUncertaintySet):
|
21
|
+
r"""Bootstrap Mu Uncertainty set.
|
22
|
+
|
23
|
+
Compute the expected returns ellipsoidal uncertainty set using circular bootstrap:
|
24
|
+
|
25
|
+
.. math:: U_{\mu}=\left\{\mu\,|\left(\mu-\hat{\mu}\right)S^{-1}\left(\mu-\hat{\mu}\right)^{T}\leq\kappa^{2}\right\}
|
26
|
+
|
27
|
+
The size of the ellipsoid :math:`\kappa` (confidence region), is computed using:
|
28
|
+
|
29
|
+
.. math:: \kappa^2 = \chi^2_{n\_assets} (\beta)
|
30
|
+
|
31
|
+
with :math:`\chi^2_{n\_assets}(\beta)` the inverse cumulative distribution function
|
32
|
+
of the chi-squared distribution with `n_assets` degrees of freedom at the
|
33
|
+
:math:`\beta` confidence level.
|
34
|
+
|
35
|
+
The Shape of the ellipsoid :math:`S` is computed using stationary bootstrap with
|
36
|
+
the option to force the non-diagonal elements of the covariance matrix to zero.
|
37
|
+
|
38
|
+
Parameters
|
39
|
+
----------
|
40
|
+
prior_estimator : BasePrior, optional
|
41
|
+
The :ref:`prior estimator <prior>` used to estimate the assets covariance
|
42
|
+
matrix. The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`.
|
43
|
+
|
44
|
+
confidence_level : float , default=0.95
|
45
|
+
Confidence level :math:`\beta` of the inverse cumulative distribution function
|
46
|
+
of the chi-squared distribution. The default value is `0.95`.
|
47
|
+
|
48
|
+
diagonal : bool, default=True
|
49
|
+
If this is set to True, the non-diagonal elements of the covariance matrix are
|
50
|
+
set to zero.
|
51
|
+
|
52
|
+
n_bootstrap_samples : int, default=1000
|
53
|
+
Number of bootstrap samples to generate. The default value is `1000`.
|
54
|
+
|
55
|
+
block_size : float, optional
|
56
|
+
Bootstrap block size. The default (`None`) is to estimate the optimal block size
|
57
|
+
using Politis & White algorithm for all individual assets.
|
58
|
+
|
59
|
+
seed : int, optional
|
60
|
+
Random seed used to initialize the pseudo-random number generator.
|
61
|
+
|
62
|
+
Attributes
|
63
|
+
----------
|
64
|
+
uncertainty_set_ : UncertaintySet
|
65
|
+
Mu Uncertainty set :class:`~skfolio.uncertainty_set.UncertaintySet`.
|
66
|
+
|
67
|
+
prior_estimator_ : BasePrior
|
68
|
+
Fitted `prior_estimator`.
|
69
|
+
|
70
|
+
References
|
71
|
+
----------
|
72
|
+
.. [1] "Robustness properties of mean-variance portfolios",
|
73
|
+
Optimization: A Journal of Mathematical Programming and Operations Research,
|
74
|
+
Schöttle & Werner (2009).
|
75
|
+
|
76
|
+
.. [2] "Automatic Block-Length Selection for the Dependent Bootstrap",
|
77
|
+
Politis & White (2004).
|
78
|
+
|
79
|
+
.. [3] "Correction to Automatic Block-Length Selection for the Dependent
|
80
|
+
Bootstrap",
|
81
|
+
Patton, Politis & White (2009).
|
82
|
+
"""
|
83
|
+
prior_estimator_: BasePrior
|
84
|
+
|
85
|
+
def __init__(
|
86
|
+
self,
|
87
|
+
prior_estimator: BasePrior | None = None,
|
88
|
+
confidence_level: float = 0.95,
|
89
|
+
diagonal: bool = True,
|
90
|
+
n_bootstrap_samples: int = 1000,
|
91
|
+
block_size: float | None = None,
|
92
|
+
seed: int | None = None,
|
93
|
+
):
|
94
|
+
self.prior_estimator = prior_estimator
|
95
|
+
self.confidence_level = confidence_level
|
96
|
+
self.diagonal = diagonal
|
97
|
+
self.n_bootstrap_samples = n_bootstrap_samples
|
98
|
+
self.block_size = block_size
|
99
|
+
self.seed = seed
|
100
|
+
|
101
|
+
def fit(
|
102
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
|
103
|
+
) -> "BootstrapMuUncertaintySet":
|
104
|
+
"""Fit the Bootstrap Mu Uncertainty set estimator.
|
105
|
+
|
106
|
+
Parameters
|
107
|
+
----------
|
108
|
+
X : array-like of shape (n_observations, n_assets)
|
109
|
+
Price returns of the assets.
|
110
|
+
|
111
|
+
y : array-like of shape (n_observations, n_factors), optional
|
112
|
+
Price returns of factors.
|
113
|
+
The default is `None`.
|
114
|
+
|
115
|
+
Returns
|
116
|
+
-------
|
117
|
+
self : BootstrapMuUncertaintySet
|
118
|
+
Fitted estimator.
|
119
|
+
"""
|
120
|
+
self.prior_estimator_ = check_estimator(
|
121
|
+
self.prior_estimator,
|
122
|
+
default=EmpiricalPrior(),
|
123
|
+
check_type=BasePrior,
|
124
|
+
)
|
125
|
+
# fitting estimators
|
126
|
+
self.prior_estimator_.fit(X, y)
|
127
|
+
mu = self.prior_estimator_.prior_model_.mu
|
128
|
+
returns = self.prior_estimator_.prior_model_.returns
|
129
|
+
n_assets = returns.shape[1]
|
130
|
+
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets))
|
131
|
+
samples = stationary_bootstrap(
|
132
|
+
returns=returns,
|
133
|
+
block_size=self.block_size,
|
134
|
+
n_bootstrap_samples=self.n_bootstrap_samples,
|
135
|
+
seed=self.seed,
|
136
|
+
)
|
137
|
+
mus = np.mean(samples, axis=1)
|
138
|
+
covs = np.zeros((self.n_bootstrap_samples, n_assets, n_assets))
|
139
|
+
for i in range(self.n_bootstrap_samples):
|
140
|
+
covs[i] = np.cov(samples[i].T)
|
141
|
+
|
142
|
+
sigma = np.cov((mus - mu).T)
|
143
|
+
if self.diagonal:
|
144
|
+
sigma = np.diag(np.diag(sigma))
|
145
|
+
|
146
|
+
self.uncertainty_set_ = UncertaintySet(k=k, sigma=sigma)
|
147
|
+
return self
|
148
|
+
|
149
|
+
|
150
|
+
class BootstrapCovarianceUncertaintySet(BaseCovarianceUncertaintySet):
|
151
|
+
r"""Bootstrap Covariance Uncertainty set.
|
152
|
+
|
153
|
+
Compute the covariance ellipsoidal uncertainty set using circular bootstrap:
|
154
|
+
|
155
|
+
.. math:: U_{\Sigma}=\left\{\Sigma\,|\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)S^{-1}\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)^{T}\leq k^{2}\,,\,\Sigma\succeq 0\right\}
|
156
|
+
|
157
|
+
The size of the ellipsoid :math:`\kappa` (confidence region), is computed using:
|
158
|
+
|
159
|
+
.. math:: \kappa^2 = \chi^2_{n\_assets^2} (\beta)
|
160
|
+
|
161
|
+
with :math:`\chi^2_{n\_assets^2}(\beta)` the inverse cumulative distribution
|
162
|
+
function of the chi-squared distribution with `n_assets` degrees of freedom at the
|
163
|
+
:math:`\beta` confidence level.
|
164
|
+
|
165
|
+
The Shape of the ellipsoid :math:`S` is computed using stationary bootstrap with the
|
166
|
+
option to force the non-diagonal elements of the covariance matrix to zero.
|
167
|
+
|
168
|
+
Parameters
|
169
|
+
----------
|
170
|
+
prior_estimator : BasePrior, optional
|
171
|
+
The :ref:`prior estimator <prior>` used to estimate the assets covariance
|
172
|
+
matrix. The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`.
|
173
|
+
|
174
|
+
confidence_level : float , default=0.95
|
175
|
+
Confidence level :math:`\beta` of the inverse cumulative distribution function
|
176
|
+
of the chi-squared distribution. The default value is `0.95`.
|
177
|
+
|
178
|
+
diagonal : bool, default=True
|
179
|
+
If this is set to True, the non-diagonal elements of the covariance matrix are
|
180
|
+
set to zero.
|
181
|
+
|
182
|
+
n_bootstrap_samples : int, default=1000
|
183
|
+
Number of bootstrap samples to generate. The default value is `1000`.
|
184
|
+
|
185
|
+
block_size : float, optional
|
186
|
+
Bootstrap block size. The default (`None`) is to estimate the optimal block size
|
187
|
+
using Politis & White algorithm for all individual assets.
|
188
|
+
|
189
|
+
seed : int, optional
|
190
|
+
Random seed used to initialize the pseudo-random number generator
|
191
|
+
|
192
|
+
Attributes
|
193
|
+
----------
|
194
|
+
uncertainty_set_ : UncertaintySet
|
195
|
+
Covariance Uncertainty set :class:`~skfolio.uncertainty_set.UncertaintySet`.
|
196
|
+
|
197
|
+
prior_estimator_ : BasePrior
|
198
|
+
Fitted `prior_estimator`.
|
199
|
+
|
200
|
+
References
|
201
|
+
----------
|
202
|
+
.. [1] "Robustness properties of mean-variance portfolios",
|
203
|
+
Optimization: A Journal of Mathematical Programming and Operations Research,
|
204
|
+
Schöttle & Werner (2009).
|
205
|
+
|
206
|
+
.. [2] "Automatic Block-Length Selection for the Dependent Bootstrap",
|
207
|
+
Politis & White (2004).
|
208
|
+
|
209
|
+
.. [3] "Correction to Automatic Block-Length Selection for the Dependent
|
210
|
+
Bootstrap",
|
211
|
+
Patton, Politis & White (2009).
|
212
|
+
"""
|
213
|
+
|
214
|
+
prior_estimator_: BasePrior
|
215
|
+
|
216
|
+
def __init__(
|
217
|
+
self,
|
218
|
+
prior_estimator: BasePrior | None = None,
|
219
|
+
confidence_level: float = 0.95,
|
220
|
+
diagonal: bool = True,
|
221
|
+
n_bootstrap_samples: int = 1000,
|
222
|
+
block_size: float | None = None,
|
223
|
+
seed: int | None = None,
|
224
|
+
):
|
225
|
+
self.prior_estimator = prior_estimator
|
226
|
+
self.confidence_level = confidence_level
|
227
|
+
self.diagonal = diagonal
|
228
|
+
self.n_bootstrap_samples = n_bootstrap_samples
|
229
|
+
self.block_size = block_size
|
230
|
+
self.seed = seed
|
231
|
+
|
232
|
+
def fit(self, X: npt.ArrayLike, y=None) -> "BootstrapCovarianceUncertaintySet":
|
233
|
+
"""Fit the Bootstrap Covariance Uncertainty set estimator.
|
234
|
+
|
235
|
+
Parameters
|
236
|
+
----------
|
237
|
+
X : array-like of shape (n_observations, n_assets)
|
238
|
+
Price returns of the assets.
|
239
|
+
|
240
|
+
y : array-like of shape (n_observations, n_factors), optional
|
241
|
+
Price returns of factors.
|
242
|
+
The default is `None`.
|
243
|
+
|
244
|
+
Returns
|
245
|
+
-------
|
246
|
+
self : EmpiricalCovarianceUncertaintySet
|
247
|
+
Fitted estimator.
|
248
|
+
"""
|
249
|
+
|
250
|
+
self.prior_estimator_ = check_estimator(
|
251
|
+
self.prior_estimator,
|
252
|
+
default=EmpiricalPrior(),
|
253
|
+
check_type=BasePrior,
|
254
|
+
)
|
255
|
+
# fitting estimators
|
256
|
+
self.prior_estimator_.fit(X, y)
|
257
|
+
covariance = self.prior_estimator_.prior_model_.covariance
|
258
|
+
returns = self.prior_estimator_.prior_model_.returns
|
259
|
+
n_assets = returns.shape[1]
|
260
|
+
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets**2))
|
261
|
+
|
262
|
+
samples = stationary_bootstrap(
|
263
|
+
returns=returns,
|
264
|
+
block_size=self.block_size,
|
265
|
+
n_bootstrap_samples=self.n_bootstrap_samples,
|
266
|
+
seed=self.seed,
|
267
|
+
)
|
268
|
+
covs = np.zeros((self.n_bootstrap_samples, n_assets, n_assets))
|
269
|
+
for i in range(self.n_bootstrap_samples):
|
270
|
+
covs[i] = np.cov(samples[i].T)
|
271
|
+
|
272
|
+
sigma = np.cov(
|
273
|
+
(covs - covariance)
|
274
|
+
.reshape((self.n_bootstrap_samples, n_assets**2), order="F")
|
275
|
+
.T
|
276
|
+
)
|
277
|
+
if self.diagonal:
|
278
|
+
sigma = np.diag(np.diag(sigma))
|
279
|
+
|
280
|
+
self.uncertainty_set_ = UncertaintySet(k=k, sigma=sigma)
|
281
|
+
return self
|
@@ -0,0 +1,237 @@
|
|
1
|
+
"""Empirical Uncertainty Set estimators."""
|
2
|
+
|
3
|
+
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
4
|
+
# License: BSD 3 clause
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
import numpy.typing as npt
|
8
|
+
import scipy.stats as st
|
9
|
+
|
10
|
+
from skfolio.prior import BasePrior, EmpiricalPrior
|
11
|
+
from skfolio.uncertainty_set._base import (
|
12
|
+
BaseCovarianceUncertaintySet,
|
13
|
+
BaseMuUncertaintySet,
|
14
|
+
UncertaintySet,
|
15
|
+
)
|
16
|
+
from skfolio.utils.stats import commutation_matrix
|
17
|
+
from skfolio.utils.tools import check_estimator
|
18
|
+
|
19
|
+
|
20
|
+
class EmpiricalMuUncertaintySet(BaseMuUncertaintySet):
|
21
|
+
r"""Empirical Mu Uncertainty Set.
|
22
|
+
|
23
|
+
Compute the expected returns ellipsoidal uncertainty set [1]_:
|
24
|
+
|
25
|
+
.. math:: U_{\mu}=\left\{\mu\,|\left(\mu-\hat{\mu}\right)S^{-1}\left(\mu-\hat{\mu}\right)^{T}\leq\kappa^{2}\right\}
|
26
|
+
|
27
|
+
Under the assumption that :math:`\Sigma` is given, the distribution of the sample
|
28
|
+
estimator :math:`\hat{\mu}` based on an i.i.d. sample
|
29
|
+
:math:`R_{t}\sim N(\mu, \Sigma), t=1,...,T` is given by
|
30
|
+
:math:`\hat{\mu}\sim N(\mu, \frac{1}{T}\Sigma)`.
|
31
|
+
|
32
|
+
The size of the ellipsoid :math:`\kappa` (confidence region), is computed using:
|
33
|
+
|
34
|
+
.. math:: \kappa^2 = \chi^2_{n\_assets} (\beta)
|
35
|
+
|
36
|
+
with :math:`\chi^2_{n\_assets}(\beta)` the inverse cumulative distribution function
|
37
|
+
of the chi-squared distribution with `n_assets` degrees of freedom at the
|
38
|
+
:math:`\beta` confidence level.
|
39
|
+
|
40
|
+
The Shape of the ellipsoid :math:`S` is computed using:
|
41
|
+
|
42
|
+
.. math:: S = \frac{1}{T}\Sigma
|
43
|
+
|
44
|
+
with the option to force the non-diagonal elements of the covariance matrix to zero.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
prior_estimator : BasePrior, optional
|
49
|
+
The :ref:`prior estimator <prior>` used to estimate the assets covariance
|
50
|
+
matrix. The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`.
|
51
|
+
|
52
|
+
confidence_level : float , default=0.95
|
53
|
+
Confidence level :math:`\beta` of the inverse cumulative distribution function
|
54
|
+
of the chi-squared distribution. The default value is `0.95`.
|
55
|
+
|
56
|
+
diagonal : bool, default=True
|
57
|
+
If this is set to True, the non-diagonal elements of the covariance matrix are
|
58
|
+
set to zero.
|
59
|
+
|
60
|
+
Attributes
|
61
|
+
----------
|
62
|
+
uncertainty_set_ : UncertaintySet
|
63
|
+
Mu Uncertainty set :class:`~skfolio.uncertainty_set.UncertaintySet`.
|
64
|
+
|
65
|
+
prior_estimator_ : BasePrior
|
66
|
+
Fitted `prior_estimator`.
|
67
|
+
|
68
|
+
References
|
69
|
+
----------
|
70
|
+
.. [1] "Robustness properties of mean-variance portfolios",
|
71
|
+
Optimization: A Journal of Mathematical Programming and Operations Research,
|
72
|
+
Schöttle & Werner (2009).
|
73
|
+
"""
|
74
|
+
prior_estimator_: BasePrior
|
75
|
+
|
76
|
+
def __init__(
|
77
|
+
self,
|
78
|
+
prior_estimator: BasePrior | None = None,
|
79
|
+
confidence_level: float = 0.95,
|
80
|
+
diagonal: bool = True,
|
81
|
+
):
|
82
|
+
self.prior_estimator = prior_estimator
|
83
|
+
self.confidence_level = confidence_level
|
84
|
+
self.diagonal = diagonal
|
85
|
+
|
86
|
+
def fit(
|
87
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
|
88
|
+
) -> "EmpiricalMuUncertaintySet":
|
89
|
+
"""Fit the Empirical Mu Uncertainty set estimator.
|
90
|
+
|
91
|
+
Parameters
|
92
|
+
----------
|
93
|
+
X : array-like of shape (n_observations, n_assets)
|
94
|
+
Price returns of the assets.
|
95
|
+
|
96
|
+
y : array-like of shape (n_observations, n_factors), optional
|
97
|
+
Price returns of factors.
|
98
|
+
The default is `None`.
|
99
|
+
|
100
|
+
Returns
|
101
|
+
-------
|
102
|
+
self : EmpiricalMuUncertaintySet
|
103
|
+
Fitted estimator.
|
104
|
+
"""
|
105
|
+
self.prior_estimator_ = check_estimator(
|
106
|
+
self.prior_estimator,
|
107
|
+
default=EmpiricalPrior(),
|
108
|
+
check_type=BasePrior,
|
109
|
+
)
|
110
|
+
# fitting estimators
|
111
|
+
self.prior_estimator_.fit(X, y)
|
112
|
+
|
113
|
+
prior_model = self.prior_estimator_.prior_model_
|
114
|
+
n_observations, n_assets = prior_model.returns.shape
|
115
|
+
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets))
|
116
|
+
|
117
|
+
sigma = prior_model.covariance / n_observations
|
118
|
+
if self.diagonal:
|
119
|
+
sigma = np.diag(np.diag(sigma))
|
120
|
+
|
121
|
+
self.uncertainty_set_ = UncertaintySet(k=k, sigma=sigma)
|
122
|
+
return self
|
123
|
+
|
124
|
+
|
125
|
+
class EmpiricalCovarianceUncertaintySet(BaseCovarianceUncertaintySet):
|
126
|
+
r"""Empirical Covariance Uncertainty set.
|
127
|
+
|
128
|
+
Compute the covariance ellipsoidal uncertainty set [1]_:
|
129
|
+
|
130
|
+
.. math:: U_{\Sigma}=\left\{\Sigma\,|\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)S^{-1}\left(\text{vec}(\Sigma)-\text{vec}(\hat{\Sigma})\right)^{T}\leq k^{2}\,,\,\Sigma\succeq 0\right\}
|
131
|
+
|
132
|
+
We consider the Wishart distribution for the covariance matrix:
|
133
|
+
|
134
|
+
.. math:: \hat{\Sigma}\sim W(\frac{1}{T-1}\Sigma, T-1)
|
135
|
+
|
136
|
+
The size of the ellipsoid :math:`\kappa` (confidence region), is computed using:
|
137
|
+
|
138
|
+
.. math:: \kappa^2 = \chi^2_{n\_assets^2} (\beta)
|
139
|
+
|
140
|
+
with :math:`\chi^2_{n\_assets^2}(\beta)` the inverse cumulative distribution
|
141
|
+
function of the chi-squared distribution with `n_assets` degrees of freedom at the
|
142
|
+
:math:`\beta` confidence level.
|
143
|
+
|
144
|
+
The Shape of the ellipsoid :math:`S` is based on a closed form solution of the
|
145
|
+
covariance matrix of the Wishart distributed random variable by using the vector
|
146
|
+
notation :math:`vec(x)`:
|
147
|
+
|
148
|
+
.. math:: Cov[vec(\hat{\Sigma})]=\frac{1}{T-1}(I_{n^2} + K_{nn})(\Sigma \otimes \Sigma)
|
149
|
+
|
150
|
+
with :math:`K_{nn}` denoting a commutation matrix and :math:`\otimes` representing
|
151
|
+
the Kronecker product.
|
152
|
+
|
153
|
+
Parameters
|
154
|
+
----------
|
155
|
+
prior_estimator : BasePrior, optional
|
156
|
+
The :ref:`prior estimator <prior>` used to estimate the assets covariance
|
157
|
+
matrix. The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`.
|
158
|
+
|
159
|
+
confidence_level : float , default=0.95
|
160
|
+
Confidence level :math:`\beta` of the inverse cumulative distribution function
|
161
|
+
of the chi-squared distribution. The default value is `0.95`.
|
162
|
+
|
163
|
+
diagonal : bool, default=True
|
164
|
+
If this is set to True, the non-diagonal elements of the covariance matrix are
|
165
|
+
set to zero.
|
166
|
+
|
167
|
+
Attributes
|
168
|
+
----------
|
169
|
+
uncertainty_set_ : UncertaintySet
|
170
|
+
Covariance Uncertainty set :class:`~skfolio.uncertainty_set.UncertaintySet`.
|
171
|
+
|
172
|
+
prior_estimator_ : BasePrior
|
173
|
+
Fitted `prior_estimator`.
|
174
|
+
|
175
|
+
References
|
176
|
+
----------
|
177
|
+
.. [1] "Robustness properties of mean-variance portfolios",
|
178
|
+
Optimization: A Journal of Mathematical Programming and Operations Research,
|
179
|
+
Schöttle & Werner (2009).
|
180
|
+
"""
|
181
|
+
prior_estimator_: BasePrior
|
182
|
+
|
183
|
+
def __init__(
|
184
|
+
self,
|
185
|
+
prior_estimator: BasePrior | None = None,
|
186
|
+
confidence_level: float = 0.95,
|
187
|
+
diagonal: bool = True,
|
188
|
+
):
|
189
|
+
self.prior_estimator = prior_estimator
|
190
|
+
self.confidence_level = confidence_level
|
191
|
+
self.diagonal = diagonal
|
192
|
+
|
193
|
+
def fit(
|
194
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
|
195
|
+
) -> "EmpiricalCovarianceUncertaintySet":
|
196
|
+
"""Fit the Empirical Covariance Uncertainty set estimator.
|
197
|
+
|
198
|
+
Parameters
|
199
|
+
----------
|
200
|
+
X : array-like of shape (n_observations, n_assets)
|
201
|
+
Price returns of the assets.
|
202
|
+
|
203
|
+
y : array-like of shape (n_observations, n_factors), optional
|
204
|
+
Price returns of factors.
|
205
|
+
The default is `None`.
|
206
|
+
|
207
|
+
Returns
|
208
|
+
-------
|
209
|
+
self : EmpiricalCovarianceUncertaintySet
|
210
|
+
Fitted estimator.
|
211
|
+
"""
|
212
|
+
self.prior_estimator_ = check_estimator(
|
213
|
+
self.prior_estimator,
|
214
|
+
default=EmpiricalPrior(),
|
215
|
+
check_type=BasePrior,
|
216
|
+
)
|
217
|
+
# fitting estimators
|
218
|
+
self.prior_estimator_.fit(X, y)
|
219
|
+
|
220
|
+
prior_model = self.prior_estimator_.prior_model_
|
221
|
+
n_observations, n_assets = prior_model.returns.shape
|
222
|
+
k = np.sqrt(st.chi2.ppf(q=self.confidence_level, df=n_assets**2))
|
223
|
+
|
224
|
+
sigma = prior_model.covariance / n_observations
|
225
|
+
if self.diagonal:
|
226
|
+
sigma = np.diag(np.diag(sigma))
|
227
|
+
|
228
|
+
sigma = np.diag(
|
229
|
+
np.diag(
|
230
|
+
n_observations
|
231
|
+
* (np.identity(n_assets**2) + commutation_matrix(sigma))
|
232
|
+
@ np.kron(sigma, sigma)
|
233
|
+
)
|
234
|
+
)
|
235
|
+
|
236
|
+
self.uncertainty_set_ = UncertaintySet(k=k, sigma=sigma)
|
237
|
+
return self
|
File without changes
|