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
@@ -11,6 +11,7 @@ import numpy as np
|
|
11
11
|
import numpy.typing as npt
|
12
12
|
import pandas as pd
|
13
13
|
import sklearn as sk
|
14
|
+
import sklearn.utils.metadata_routing as skm
|
14
15
|
|
15
16
|
import skfolio.typing as skt
|
16
17
|
from skfolio.measures import RiskMeasure
|
@@ -681,7 +682,25 @@ class MeanRisk(ConvexOptimization):
|
|
681
682
|
"`objective_function = ObjectiveFunction.MINIMIZE_RISK`"
|
682
683
|
)
|
683
684
|
|
684
|
-
def
|
685
|
+
def get_metadata_routing(self):
|
686
|
+
# noinspection PyTypeChecker
|
687
|
+
router = (
|
688
|
+
super()
|
689
|
+
.get_metadata_routing()
|
690
|
+
.add(
|
691
|
+
mu_uncertainty_set_estimator=self.mu_uncertainty_set_estimator,
|
692
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
693
|
+
)
|
694
|
+
.add(
|
695
|
+
covariance_uncertainty_set_estimator=self.covariance_uncertainty_set_estimator,
|
696
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
697
|
+
)
|
698
|
+
)
|
699
|
+
return router
|
700
|
+
|
701
|
+
def fit(
|
702
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None, **fit_params
|
703
|
+
) -> "MeanRisk":
|
685
704
|
"""Fit the Mean-Risk Optimization estimator.
|
686
705
|
|
687
706
|
Parameters
|
@@ -698,6 +717,8 @@ class MeanRisk(ConvexOptimization):
|
|
698
717
|
self : MeanRisk
|
699
718
|
Fitted estimator.
|
700
719
|
"""
|
720
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
721
|
+
|
701
722
|
self._check_feature_names(X, reset=True)
|
702
723
|
# Validate
|
703
724
|
self._validation()
|
@@ -708,7 +729,7 @@ class MeanRisk(ConvexOptimization):
|
|
708
729
|
default=EmpiricalPrior(),
|
709
730
|
check_type=BasePrior,
|
710
731
|
)
|
711
|
-
self.prior_estimator_.fit(X, y)
|
732
|
+
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
712
733
|
prior_model = self.prior_estimator_.prior_model_
|
713
734
|
n_observations, n_assets = prior_model.returns.shape
|
714
735
|
|
@@ -766,7 +787,9 @@ class MeanRisk(ConvexOptimization):
|
|
766
787
|
self.mu_uncertainty_set_estimator_ = sk.clone(
|
767
788
|
self.mu_uncertainty_set_estimator
|
768
789
|
)
|
769
|
-
self.mu_uncertainty_set_estimator_.fit(
|
790
|
+
self.mu_uncertainty_set_estimator_.fit(
|
791
|
+
X, y, **routed_params.mu_uncertainty_set_estimator.fit
|
792
|
+
)
|
770
793
|
mu_uncertainty_set = self._cvx_mu_uncertainty_set(
|
771
794
|
mu_uncertainty_set=self.mu_uncertainty_set_estimator_.uncertainty_set_,
|
772
795
|
w=w,
|
@@ -831,11 +854,11 @@ class MeanRisk(ConvexOptimization):
|
|
831
854
|
efficient_frontier_size=None,
|
832
855
|
portfolio_params=dict(annualized_factor=1),
|
833
856
|
)
|
834
|
-
model.fit(X)
|
857
|
+
model.fit(X, y, **fit_params)
|
835
858
|
min_return = model.problem_values_["expected_return"]
|
836
859
|
# noinspection PyTypeChecker
|
837
860
|
model.set_params(objective_function=ObjectiveFunction.MAXIMIZE_RETURN)
|
838
|
-
model.fit(X)
|
861
|
+
model.fit(X, y, **fit_params)
|
839
862
|
max_return = model.problem_values_["expected_return"]
|
840
863
|
if max_return <= 0:
|
841
864
|
raise ValueError(
|
@@ -888,7 +911,11 @@ class MeanRisk(ConvexOptimization):
|
|
888
911
|
self.covariance_uncertainty_set_estimator_ = sk.clone(
|
889
912
|
self.covariance_uncertainty_set_estimator
|
890
913
|
)
|
891
|
-
self.covariance_uncertainty_set_estimator_.fit(
|
914
|
+
self.covariance_uncertainty_set_estimator_.fit(
|
915
|
+
X,
|
916
|
+
y,
|
917
|
+
**routed_params.covariance_uncertainty_set_estimator.fit,
|
918
|
+
)
|
892
919
|
args[arg_name] = (
|
893
920
|
self.covariance_uncertainty_set_estimator_.uncertainty_set_
|
894
921
|
)
|
@@ -9,6 +9,7 @@
|
|
9
9
|
import cvxpy as cp
|
10
10
|
import numpy as np
|
11
11
|
import numpy.typing as npt
|
12
|
+
import sklearn.utils.metadata_routing as skm
|
12
13
|
|
13
14
|
import skfolio.typing as skt
|
14
15
|
from skfolio.measures import RiskMeasure
|
@@ -440,7 +441,7 @@ class RiskBudgeting(ConvexOptimization):
|
|
440
441
|
" otherwise the problem becomes non-convex."
|
441
442
|
)
|
442
443
|
|
443
|
-
def fit(self, X: npt.ArrayLike, y=None) -> "RiskBudgeting":
|
444
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params) -> "RiskBudgeting":
|
444
445
|
"""Fit the Risk Budgeting Optimization estimator.
|
445
446
|
|
446
447
|
Parameters
|
@@ -458,6 +459,8 @@ class RiskBudgeting(ConvexOptimization):
|
|
458
459
|
self : RiskBudgeting
|
459
460
|
Fitted estimator.
|
460
461
|
"""
|
462
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
463
|
+
|
461
464
|
self._check_feature_names(X, reset=True)
|
462
465
|
# Validate
|
463
466
|
self._validation()
|
@@ -468,7 +471,7 @@ class RiskBudgeting(ConvexOptimization):
|
|
468
471
|
default=EmpiricalPrior(),
|
469
472
|
check_type=BasePrior,
|
470
473
|
)
|
471
|
-
self.prior_estimator_.fit(X, y)
|
474
|
+
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
472
475
|
prior_model = self.prior_estimator_.prior_model_
|
473
476
|
n_observations, n_assets = prior_model.returns.shape
|
474
477
|
|
@@ -12,8 +12,9 @@ from copy import deepcopy
|
|
12
12
|
import numpy as np
|
13
13
|
import numpy.typing as npt
|
14
14
|
import sklearn as sk
|
15
|
-
import sklearn.model_selection as
|
15
|
+
import sklearn.model_selection as sks
|
16
16
|
import sklearn.utils as sku
|
17
|
+
import sklearn.utils.metadata_routing as skm
|
17
18
|
import sklearn.utils.parallel as skp
|
18
19
|
import sklearn.utils.validation as skv
|
19
20
|
|
@@ -138,7 +139,7 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
138
139
|
self,
|
139
140
|
estimators: list[tuple[str, BaseOptimization]],
|
140
141
|
final_estimator: BaseOptimization | None = None,
|
141
|
-
cv:
|
142
|
+
cv: sks.BaseCrossValidator | BaseCombinatorialCV | str | int | None = None,
|
142
143
|
quantile: float = 0.5,
|
143
144
|
quantile_measure: skt.Measure = RatioMeasure.SHARPE_RATIO,
|
144
145
|
n_jobs: int | None = None,
|
@@ -229,8 +230,18 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
229
230
|
"""
|
230
231
|
return super()._get_params("estimators", deep=deep)
|
231
232
|
|
233
|
+
def get_metadata_routing(self):
|
234
|
+
# noinspection PyTypeChecker
|
235
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__)
|
236
|
+
for name, estimator in self.estimators:
|
237
|
+
router.add(
|
238
|
+
**{name: estimator},
|
239
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
240
|
+
)
|
241
|
+
return router
|
242
|
+
|
232
243
|
def fit(
|
233
|
-
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
|
244
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None, **fit_params
|
234
245
|
) -> "StackingOptimization":
|
235
246
|
"""Fit the Stacking Optimization estimator.
|
236
247
|
|
@@ -243,11 +254,20 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
243
254
|
Price returns of factors or a target benchmark.
|
244
255
|
The default is `None`.
|
245
256
|
|
257
|
+
**fit_params : dict
|
258
|
+
Parameters to pass to the underlying estimators.
|
259
|
+
Only available if `enable_metadata_routing=True`, which can be
|
260
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
261
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
262
|
+
more details.
|
263
|
+
|
246
264
|
Returns
|
247
265
|
-------
|
248
266
|
self : StackingOptimization
|
249
267
|
Fitted estimator.
|
250
268
|
"""
|
269
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
270
|
+
|
251
271
|
names, all_estimators = self._validate_estimators()
|
252
272
|
self.final_estimator_ = check_estimator(
|
253
273
|
self.final_estimator,
|
@@ -266,8 +286,10 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
266
286
|
# They are exposed publicly.
|
267
287
|
# noinspection PyCallingNonCallable
|
268
288
|
self.estimators_ = skp.Parallel(n_jobs=self.n_jobs)(
|
269
|
-
skp.delayed(fit_single_estimator)(
|
270
|
-
|
289
|
+
skp.delayed(fit_single_estimator)(
|
290
|
+
sk.clone(est), X, y, routed_params[name]["fit"]
|
291
|
+
)
|
292
|
+
for name, est in zip(names, all_estimators, strict=True)
|
271
293
|
)
|
272
294
|
|
273
295
|
self.named_estimators_ = {
|
@@ -287,21 +309,22 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
287
309
|
[estimator.predict(X) for estimator in self.estimators_]
|
288
310
|
).T
|
289
311
|
else:
|
290
|
-
cv =
|
312
|
+
cv = sks.check_cv(self.cv)
|
291
313
|
if hasattr(cv, "random_state") and cv.random_state is None:
|
292
314
|
cv.random_state = np.random.RandomState()
|
293
315
|
# noinspection PyCallingNonCallable
|
294
316
|
cv_predictions = skp.Parallel(n_jobs=self.n_jobs)(
|
295
317
|
skp.delayed(cross_val_predict)(
|
296
|
-
sk.clone(
|
318
|
+
sk.clone(est),
|
297
319
|
X,
|
298
320
|
y,
|
299
321
|
cv=deepcopy(cv),
|
300
322
|
method="predict",
|
301
323
|
n_jobs=self.n_jobs,
|
324
|
+
params=routed_params[name]["fit"],
|
302
325
|
verbose=self.verbose,
|
303
326
|
)
|
304
|
-
for
|
327
|
+
for name, est in zip(names, all_estimators, strict=True)
|
305
328
|
)
|
306
329
|
|
307
330
|
# We validate and convert to numpy array only after base-estimator fitting
|
@@ -326,7 +349,7 @@ class StackingOptimization(BaseOptimization, BaseComposition):
|
|
326
349
|
)
|
327
350
|
y = y[test_indices]
|
328
351
|
|
329
|
-
fit_single_estimator(self.final_estimator_, X_pred, y)
|
352
|
+
fit_single_estimator(self.final_estimator_, X_pred, y, {})
|
330
353
|
outer_weights = self.final_estimator_.weights_
|
331
354
|
self.weights_ = outer_weights @ inner_weights
|
332
355
|
return self
|
@@ -5,6 +5,7 @@
|
|
5
5
|
|
6
6
|
import numpy as np
|
7
7
|
import numpy.typing as npt
|
8
|
+
import sklearn.utils.metadata_routing as skm
|
8
9
|
|
9
10
|
from skfolio.optimization._base import BaseOptimization
|
10
11
|
from skfolio.prior import BasePrior, EmpiricalPrior
|
@@ -53,8 +54,16 @@ class InverseVolatility(BaseOptimization):
|
|
53
54
|
super().__init__(portfolio_params=portfolio_params)
|
54
55
|
self.prior_estimator = prior_estimator
|
55
56
|
|
57
|
+
def get_metadata_routing(self):
|
58
|
+
# noinspection PyTypeChecker
|
59
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
60
|
+
prior_estimator=self.prior_estimator,
|
61
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
62
|
+
)
|
63
|
+
return router
|
64
|
+
|
56
65
|
def fit(
|
57
|
-
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
|
66
|
+
self, X: npt.ArrayLike, y: npt.ArrayLike | None = None, **fit_params
|
58
67
|
) -> "InverseVolatility":
|
59
68
|
"""Fit the Inverse Volatility estimator.
|
60
69
|
|
@@ -67,18 +76,27 @@ class InverseVolatility(BaseOptimization):
|
|
67
76
|
Price returns of factors or a target benchmark.
|
68
77
|
The default is `None`.
|
69
78
|
|
79
|
+
**fit_params : dict
|
80
|
+
Parameters to pass to the underlying estimators.
|
81
|
+
Only available if `enable_metadata_routing=True`, which can be
|
82
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
83
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
84
|
+
more details.
|
85
|
+
|
70
86
|
Returns
|
71
87
|
-------
|
72
88
|
self : InverseVolatility
|
73
89
|
Fitted estimator.
|
74
90
|
"""
|
91
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
92
|
+
|
75
93
|
# fitting prior estimator
|
76
94
|
self.prior_estimator_ = check_estimator(
|
77
95
|
self.prior_estimator,
|
78
96
|
default=EmpiricalPrior(),
|
79
97
|
check_type=BasePrior,
|
80
98
|
)
|
81
|
-
self.prior_estimator_.fit(X, y)
|
99
|
+
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
82
100
|
covariance = self.prior_estimator_.prior_model_.covariance
|
83
101
|
w = 1 / np.sqrt(np.diag(covariance))
|
84
102
|
self.weights_ = w / sum(w)
|
@@ -612,6 +612,8 @@ class Population(list):
|
|
612
612
|
title = f"{title} (non-compounded)"
|
613
613
|
|
614
614
|
df = pd.concat(cumulative_returns, axis=1).iloc[:, idx]
|
615
|
+
# Sort index because pd.concat unsort NaNs at the end
|
616
|
+
df.sort_index(inplace=True)
|
615
617
|
df.columns = deduplicate_names(names)
|
616
618
|
|
617
619
|
fig = df.plot(backend="plotly")
|
skfolio/prior/_base.py
CHANGED
@@ -9,6 +9,7 @@
|
|
9
9
|
|
10
10
|
import numpy as np
|
11
11
|
import numpy.typing as npt
|
12
|
+
import sklearn.utils.metadata_routing as skm
|
12
13
|
|
13
14
|
from skfolio.moments import EquilibriumMu
|
14
15
|
from skfolio.prior._base import BasePrior, PriorModel
|
@@ -134,7 +135,15 @@ class BlackLitterman(BasePrior):
|
|
134
135
|
self.view_confidences = view_confidences
|
135
136
|
self.risk_free_rate = risk_free_rate
|
136
137
|
|
137
|
-
def
|
138
|
+
def get_metadata_routing(self):
|
139
|
+
# noinspection PyTypeChecker
|
140
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
141
|
+
prior_estimator=self.prior_estimator,
|
142
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
143
|
+
)
|
144
|
+
return router
|
145
|
+
|
146
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params) -> "BlackLitterman":
|
138
147
|
"""Fit the Black & Litterman estimator.
|
139
148
|
|
140
149
|
Parameters
|
@@ -145,18 +154,27 @@ class BlackLitterman(BasePrior):
|
|
145
154
|
y : Ignored
|
146
155
|
Not used, present for API consistency by convention.
|
147
156
|
|
157
|
+
**fit_params : dict
|
158
|
+
Parameters to pass to the underlying estimators.
|
159
|
+
Only available if `enable_metadata_routing=True`, which can be
|
160
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
161
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
162
|
+
more details.
|
163
|
+
|
148
164
|
Returns
|
149
165
|
-------
|
150
166
|
self : BlackLitterman
|
151
167
|
Fitted estimator.
|
152
168
|
"""
|
169
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
170
|
+
|
153
171
|
self.prior_estimator_ = check_estimator(
|
154
172
|
self.prior_estimator,
|
155
173
|
default=EmpiricalPrior(mu_estimator=EquilibriumMu()),
|
156
174
|
check_type=BasePrior,
|
157
175
|
)
|
158
176
|
# fitting prior estimator
|
159
|
-
self.prior_estimator_.fit(X, y)
|
177
|
+
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
160
178
|
|
161
179
|
prior_mu = self.prior_estimator_.prior_model_.mu
|
162
180
|
prior_covariance = self.prior_estimator_.prior_model_.covariance
|
skfolio/prior/_empirical.py
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
import numpy.typing as npt
|
9
|
+
import sklearn.utils.metadata_routing as skm
|
9
10
|
|
10
11
|
from skfolio.moments import BaseCovariance, BaseMu, EmpiricalCovariance, EmpiricalMu
|
11
12
|
from skfolio.prior._base import BasePrior, PriorModel
|
@@ -86,7 +87,22 @@ class EmpiricalPrior(BasePrior):
|
|
86
87
|
self.is_log_normal = is_log_normal
|
87
88
|
self.investment_horizon = investment_horizon
|
88
89
|
|
89
|
-
def
|
90
|
+
def get_metadata_routing(self):
|
91
|
+
# noinspection PyTypeChecker
|
92
|
+
router = (
|
93
|
+
skm.MetadataRouter(owner=self.__class__.__name__)
|
94
|
+
.add(
|
95
|
+
mu_estimator=self.mu_estimator,
|
96
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
97
|
+
)
|
98
|
+
.add(
|
99
|
+
covariance_estimator=self.covariance_estimator,
|
100
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
101
|
+
)
|
102
|
+
)
|
103
|
+
return router
|
104
|
+
|
105
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params) -> "EmpiricalPrior":
|
90
106
|
"""Fit the Empirical Prior estimator.
|
91
107
|
|
92
108
|
Parameters
|
@@ -97,11 +113,20 @@ class EmpiricalPrior(BasePrior):
|
|
97
113
|
y : Ignored
|
98
114
|
Not used, present for API consistency by convention.
|
99
115
|
|
116
|
+
**fit_params : dict
|
117
|
+
Parameters to pass to the underlying estimators.
|
118
|
+
Only available if `enable_metadata_routing=True`, which can be
|
119
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
120
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
121
|
+
more details.
|
122
|
+
|
100
123
|
Returns
|
101
124
|
-------
|
102
125
|
self : EmpiricalPrior
|
103
126
|
Fitted estimator.
|
104
127
|
"""
|
128
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
129
|
+
|
105
130
|
self.mu_estimator_ = check_estimator(
|
106
131
|
self.mu_estimator,
|
107
132
|
default=EmpiricalMu(),
|
@@ -120,11 +145,15 @@ class EmpiricalPrior(BasePrior):
|
|
120
145
|
"`is_log_normal` is `False`"
|
121
146
|
)
|
122
147
|
# Expected returns
|
123
|
-
|
148
|
+
# noinspection PyArgumentList
|
149
|
+
self.mu_estimator_.fit(X, y, **routed_params.mu_estimator.fit)
|
124
150
|
mu = self.mu_estimator_.mu_
|
125
151
|
|
126
152
|
# Covariance
|
127
|
-
|
153
|
+
# noinspection PyArgumentList
|
154
|
+
self.covariance_estimator_.fit(
|
155
|
+
X, y, **routed_params.covariance_estimator.fit
|
156
|
+
)
|
128
157
|
covariance = self.covariance_estimator_.covariance_
|
129
158
|
else:
|
130
159
|
if self.investment_horizon is None:
|
@@ -138,11 +167,15 @@ class EmpiricalPrior(BasePrior):
|
|
138
167
|
|
139
168
|
# Estimates the moments on the log returns
|
140
169
|
# Expected returns
|
141
|
-
|
170
|
+
# noinspection PyArgumentList
|
171
|
+
self.mu_estimator_.fit(X_log, y_log, **routed_params.mu_estimator.fit)
|
142
172
|
mu = self.mu_estimator_.mu_
|
143
173
|
|
144
174
|
# Covariance
|
145
|
-
|
175
|
+
# noinspection PyArgumentList
|
176
|
+
self.covariance_estimator_.fit(
|
177
|
+
X_log, y_log, **routed_params.covariance_estimator.fit
|
178
|
+
)
|
146
179
|
covariance = self.covariance_estimator_.covariance_
|
147
180
|
|
148
181
|
# Using the property of aggregation across time we scale this distribution
|
skfolio/prior/_factor_model.py
CHANGED
@@ -15,7 +15,8 @@ import numpy as np
|
|
15
15
|
import numpy.typing as npt
|
16
16
|
import sklearn.base as skb
|
17
17
|
import sklearn.linear_model as skl
|
18
|
-
import sklearn.multioutput as
|
18
|
+
import sklearn.multioutput as skmo
|
19
|
+
import sklearn.utils.metadata_routing as skm
|
19
20
|
|
20
21
|
from skfolio.prior._base import BasePrior, PriorModel
|
21
22
|
from skfolio.prior._empirical import EmpiricalPrior
|
@@ -74,7 +75,7 @@ class LoadingMatrixRegression(BaseLoadingMatrix):
|
|
74
75
|
Fitted `sklearn.multioutput.MultiOutputRegressor`
|
75
76
|
"""
|
76
77
|
|
77
|
-
multi_output_regressor_:
|
78
|
+
multi_output_regressor_: skmo.MultiOutputRegressor
|
78
79
|
|
79
80
|
def __init__(
|
80
81
|
self,
|
@@ -84,7 +85,15 @@ class LoadingMatrixRegression(BaseLoadingMatrix):
|
|
84
85
|
self.linear_regressor = linear_regressor
|
85
86
|
self.n_jobs = n_jobs
|
86
87
|
|
87
|
-
def
|
88
|
+
def get_metadata_routing(self):
|
89
|
+
# noinspection PyTypeChecker
|
90
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
91
|
+
linear_regressor=self.linear_regressor,
|
92
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
93
|
+
)
|
94
|
+
return router
|
95
|
+
|
96
|
+
def fit(self, X: npt.ArrayLike, y: npt.ArrayLike, **fit_params):
|
88
97
|
"""Fit the Loading Matrix Regression Estimator.
|
89
98
|
|
90
99
|
Parameters
|
@@ -95,21 +104,30 @@ class LoadingMatrixRegression(BaseLoadingMatrix):
|
|
95
104
|
y : array-like of shape (n_observations, n_factors)
|
96
105
|
Price returns of the factors.
|
97
106
|
|
107
|
+
**fit_params : dict
|
108
|
+
Parameters to pass to the underlying estimators.
|
109
|
+
Only available if `enable_metadata_routing=True`, which can be
|
110
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
111
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
112
|
+
more details.
|
113
|
+
|
98
114
|
Returns
|
99
115
|
-------
|
100
116
|
self : LoadingMatrixRegression
|
101
117
|
Fitted estimator.
|
102
118
|
"""
|
119
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
120
|
+
|
103
121
|
_linear_regressor = check_estimator(
|
104
122
|
self.linear_regressor,
|
105
123
|
default=skl.LassoCV(fit_intercept=False),
|
106
124
|
check_type=skb.BaseEstimator,
|
107
125
|
)
|
108
126
|
|
109
|
-
self.multi_output_regressor_ =
|
127
|
+
self.multi_output_regressor_ = skmo.MultiOutputRegressor(
|
110
128
|
_linear_regressor, n_jobs=self.n_jobs
|
111
129
|
)
|
112
|
-
self.multi_output_regressor_.fit(X=y, y=X)
|
130
|
+
self.multi_output_regressor_.fit(X=y, y=X, **routed_params.linear_regressor.fit)
|
113
131
|
# noinspection PyUnresolvedReferences
|
114
132
|
n_assets = X.shape[1]
|
115
133
|
self.loading_matrix_ = np.array(
|
@@ -196,8 +214,16 @@ class FactorModel(BasePrior):
|
|
196
214
|
self.higham = higham
|
197
215
|
self.max_iteration = max_iteration
|
198
216
|
|
217
|
+
def get_metadata_routing(self):
|
218
|
+
# noinspection PyTypeChecker
|
219
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
220
|
+
factor_prior_estimator=self.factor_prior_estimator,
|
221
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
222
|
+
)
|
223
|
+
return router
|
224
|
+
|
199
225
|
# noinspection PyMethodOverriding, PyPep8Naming
|
200
|
-
def fit(self, X: npt.ArrayLike, y: Any):
|
226
|
+
def fit(self, X: npt.ArrayLike, y: Any, **fit_params):
|
201
227
|
"""Fit the Factor Model estimator.
|
202
228
|
|
203
229
|
Parameters
|
@@ -208,11 +234,20 @@ class FactorModel(BasePrior):
|
|
208
234
|
y : array-like of shape (n_observations, n_factors)
|
209
235
|
Factors' returns.
|
210
236
|
|
237
|
+
**fit_params : dict
|
238
|
+
Parameters to pass to the underlying estimators.
|
239
|
+
Only available if `enable_metadata_routing=True`, which can be
|
240
|
+
set by using ``sklearn.set_config(enable_metadata_routing=True)``.
|
241
|
+
See :ref:`Metadata Routing User Guide <metadata_routing>` for
|
242
|
+
more details.
|
243
|
+
|
211
244
|
Returns
|
212
245
|
-------
|
213
246
|
self : FactorModel
|
214
247
|
Fitted estimator.
|
215
248
|
"""
|
249
|
+
routed_params = skm.process_routing(self, "fit", **fit_params)
|
250
|
+
|
216
251
|
self.factor_prior_estimator_ = check_estimator(
|
217
252
|
self.factor_prior_estimator,
|
218
253
|
default=EmpiricalPrior(),
|
@@ -225,7 +260,9 @@ class FactorModel(BasePrior):
|
|
225
260
|
)
|
226
261
|
|
227
262
|
# Fitting prior estimator
|
228
|
-
self.factor_prior_estimator_.fit(
|
263
|
+
self.factor_prior_estimator_.fit(
|
264
|
+
X=y, **routed_params.factor_prior_estimator.fit
|
265
|
+
)
|
229
266
|
factor_mu = self.factor_prior_estimator_.prior_model_.mu
|
230
267
|
factor_covariance = self.factor_prior_estimator_.prior_model_.covariance
|
231
268
|
|
skfolio/uncertainty_set/_base.py
CHANGED
@@ -10,6 +10,9 @@ from dataclasses import dataclass
|
|
10
10
|
import numpy as np
|
11
11
|
import numpy.typing as npt
|
12
12
|
import sklearn.base as skb
|
13
|
+
import sklearn.utils.metadata_routing as skm
|
14
|
+
|
15
|
+
from skfolio.prior import BasePrior
|
13
16
|
|
14
17
|
|
15
18
|
# frozen=True with eq=False will lead to an id-based hashing which is needed for
|
@@ -54,13 +57,22 @@ class BaseMuUncertaintySet(skb.BaseEstimator, ABC):
|
|
54
57
|
"""
|
55
58
|
|
56
59
|
uncertainty_set_: UncertaintySet
|
60
|
+
prior_estimator_: BasePrior
|
57
61
|
|
58
62
|
@abstractmethod
|
59
|
-
def __init__(self):
|
60
|
-
|
63
|
+
def __init__(self, prior_estimator: BasePrior | None = None):
|
64
|
+
self.prior_estimator = prior_estimator
|
65
|
+
|
66
|
+
def get_metadata_routing(self):
|
67
|
+
# noinspection PyTypeChecker
|
68
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
69
|
+
prior_estimator=self.prior_estimator,
|
70
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
71
|
+
)
|
72
|
+
return router
|
61
73
|
|
62
74
|
@abstractmethod
|
63
|
-
def fit(self, X: npt.ArrayLike, y=None):
|
75
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params):
|
64
76
|
pass
|
65
77
|
|
66
78
|
|
@@ -75,14 +87,11 @@ class BaseCovarianceUncertaintySet(skb.BaseEstimator, ABC):
|
|
75
87
|
"""
|
76
88
|
|
77
89
|
uncertainty_set_: UncertaintySet
|
90
|
+
prior_estimator_: BasePrior
|
78
91
|
|
79
92
|
@abstractmethod
|
80
|
-
def __init__(self):
|
81
|
-
|
82
|
-
|
83
|
-
@abstractmethod
|
84
|
-
def fit(self, X: npt.ArrayLike, y=None):
|
85
|
-
pass
|
93
|
+
def __init__(self, prior_estimator: BasePrior | None = None):
|
94
|
+
self.prior_estimator = prior_estimator
|
86
95
|
|
87
96
|
def _validate_X_y(self, X: npt.ArrayLike, y: npt.ArrayLike | None = None):
|
88
97
|
"""Validate X and y if provided.
|
@@ -108,3 +117,15 @@ class BaseCovarianceUncertaintySet(skb.BaseEstimator, ABC):
|
|
108
117
|
else:
|
109
118
|
X, y = self._validate_data(X, y, multi_output=True)
|
110
119
|
return X, y
|
120
|
+
|
121
|
+
def get_metadata_routing(self):
|
122
|
+
# noinspection PyTypeChecker
|
123
|
+
router = skm.MetadataRouter(owner=self.__class__.__name__).add(
|
124
|
+
prior_estimator=self.prior_estimator,
|
125
|
+
method_mapping=skm.MethodMapping().add(caller="fit", callee="fit"),
|
126
|
+
)
|
127
|
+
return router
|
128
|
+
|
129
|
+
@abstractmethod
|
130
|
+
def fit(self, X: npt.ArrayLike, y=None, **fit_params):
|
131
|
+
pass
|