skfolio 0.6.0__py3-none-any.whl → 0.8.0__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.
Files changed (118) hide show
  1. skfolio/__init__.py +7 -7
  2. skfolio/cluster/__init__.py +2 -2
  3. skfolio/cluster/_hierarchical.py +2 -2
  4. skfolio/datasets/__init__.py +3 -3
  5. skfolio/datasets/_base.py +2 -2
  6. skfolio/datasets/data/__init__.py +1 -0
  7. skfolio/distance/__init__.py +4 -4
  8. skfolio/distance/_base.py +2 -2
  9. skfolio/distance/_distance.py +11 -10
  10. skfolio/distribution/__init__.py +56 -0
  11. skfolio/distribution/_base.py +203 -0
  12. skfolio/distribution/copula/__init__.py +35 -0
  13. skfolio/distribution/copula/_base.py +456 -0
  14. skfolio/distribution/copula/_clayton.py +539 -0
  15. skfolio/distribution/copula/_gaussian.py +407 -0
  16. skfolio/distribution/copula/_gumbel.py +560 -0
  17. skfolio/distribution/copula/_independent.py +196 -0
  18. skfolio/distribution/copula/_joe.py +609 -0
  19. skfolio/distribution/copula/_selection.py +111 -0
  20. skfolio/distribution/copula/_student_t.py +486 -0
  21. skfolio/distribution/copula/_utils.py +509 -0
  22. skfolio/distribution/multivariate/__init__.py +11 -0
  23. skfolio/distribution/multivariate/_base.py +241 -0
  24. skfolio/distribution/multivariate/_utils.py +632 -0
  25. skfolio/distribution/multivariate/_vine_copula.py +1254 -0
  26. skfolio/distribution/univariate/__init__.py +19 -0
  27. skfolio/distribution/univariate/_base.py +308 -0
  28. skfolio/distribution/univariate/_gaussian.py +136 -0
  29. skfolio/distribution/univariate/_johnson_su.py +152 -0
  30. skfolio/distribution/univariate/_normal_inverse_gaussian.py +153 -0
  31. skfolio/distribution/univariate/_selection.py +85 -0
  32. skfolio/distribution/univariate/_student_t.py +144 -0
  33. skfolio/exceptions.py +8 -8
  34. skfolio/measures/__init__.py +24 -24
  35. skfolio/measures/_enums.py +7 -7
  36. skfolio/measures/_measures.py +4 -7
  37. skfolio/metrics/__init__.py +2 -0
  38. skfolio/metrics/_scorer.py +4 -4
  39. skfolio/model_selection/__init__.py +4 -4
  40. skfolio/model_selection/_combinatorial.py +15 -12
  41. skfolio/model_selection/_validation.py +2 -2
  42. skfolio/model_selection/_walk_forward.py +3 -3
  43. skfolio/moments/__init__.py +11 -11
  44. skfolio/moments/covariance/__init__.py +6 -6
  45. skfolio/moments/covariance/_base.py +1 -1
  46. skfolio/moments/covariance/_denoise_covariance.py +3 -2
  47. skfolio/moments/covariance/_detone_covariance.py +3 -2
  48. skfolio/moments/covariance/_empirical_covariance.py +3 -2
  49. skfolio/moments/covariance/_ew_covariance.py +3 -2
  50. skfolio/moments/covariance/_gerber_covariance.py +3 -2
  51. skfolio/moments/covariance/_graphical_lasso_cv.py +1 -1
  52. skfolio/moments/covariance/_implied_covariance.py +3 -8
  53. skfolio/moments/covariance/_ledoit_wolf.py +1 -1
  54. skfolio/moments/covariance/_oas.py +1 -1
  55. skfolio/moments/covariance/_shrunk_covariance.py +1 -1
  56. skfolio/moments/expected_returns/__init__.py +2 -2
  57. skfolio/moments/expected_returns/_base.py +1 -1
  58. skfolio/moments/expected_returns/_empirical_mu.py +3 -2
  59. skfolio/moments/expected_returns/_equilibrium_mu.py +3 -2
  60. skfolio/moments/expected_returns/_ew_mu.py +3 -2
  61. skfolio/moments/expected_returns/_shrunk_mu.py +4 -3
  62. skfolio/optimization/__init__.py +12 -10
  63. skfolio/optimization/_base.py +2 -2
  64. skfolio/optimization/cluster/__init__.py +3 -1
  65. skfolio/optimization/cluster/_nco.py +10 -9
  66. skfolio/optimization/cluster/hierarchical/__init__.py +3 -1
  67. skfolio/optimization/cluster/hierarchical/_base.py +1 -2
  68. skfolio/optimization/cluster/hierarchical/_herc.py +4 -3
  69. skfolio/optimization/cluster/hierarchical/_hrp.py +4 -3
  70. skfolio/optimization/convex/__init__.py +5 -3
  71. skfolio/optimization/convex/_base.py +10 -9
  72. skfolio/optimization/convex/_distributionally_robust.py +8 -5
  73. skfolio/optimization/convex/_maximum_diversification.py +8 -6
  74. skfolio/optimization/convex/_mean_risk.py +10 -8
  75. skfolio/optimization/convex/_risk_budgeting.py +6 -4
  76. skfolio/optimization/ensemble/__init__.py +2 -0
  77. skfolio/optimization/ensemble/_base.py +2 -2
  78. skfolio/optimization/ensemble/_stacking.py +3 -3
  79. skfolio/optimization/naive/__init__.py +3 -1
  80. skfolio/optimization/naive/_naive.py +4 -3
  81. skfolio/population/__init__.py +2 -0
  82. skfolio/population/_population.py +34 -7
  83. skfolio/portfolio/__init__.py +1 -1
  84. skfolio/portfolio/_base.py +43 -8
  85. skfolio/portfolio/_multi_period_portfolio.py +3 -2
  86. skfolio/portfolio/_portfolio.py +5 -4
  87. skfolio/pre_selection/__init__.py +3 -1
  88. skfolio/pre_selection/_drop_correlated.py +3 -3
  89. skfolio/pre_selection/_select_complete.py +31 -30
  90. skfolio/pre_selection/_select_k_extremes.py +3 -3
  91. skfolio/pre_selection/_select_non_dominated.py +3 -3
  92. skfolio/pre_selection/_select_non_expiring.py +8 -6
  93. skfolio/preprocessing/__init__.py +2 -0
  94. skfolio/preprocessing/_returns.py +2 -2
  95. skfolio/prior/__init__.py +7 -3
  96. skfolio/prior/_base.py +2 -2
  97. skfolio/prior/_black_litterman.py +7 -4
  98. skfolio/prior/_empirical.py +5 -2
  99. skfolio/prior/_factor_model.py +10 -5
  100. skfolio/prior/_synthetic_data.py +239 -0
  101. skfolio/synthetic_returns/__init__.py +1 -0
  102. skfolio/typing.py +7 -7
  103. skfolio/uncertainty_set/__init__.py +7 -5
  104. skfolio/uncertainty_set/_base.py +5 -4
  105. skfolio/uncertainty_set/_bootstrap.py +1 -1
  106. skfolio/uncertainty_set/_empirical.py +1 -1
  107. skfolio/utils/__init__.py +1 -0
  108. skfolio/utils/bootstrap.py +2 -2
  109. skfolio/utils/equations.py +13 -10
  110. skfolio/utils/sorting.py +2 -2
  111. skfolio/utils/stats.py +15 -15
  112. skfolio/utils/tools.py +86 -22
  113. {skfolio-0.6.0.dist-info → skfolio-0.8.0.dist-info}/METADATA +122 -46
  114. skfolio-0.8.0.dist-info/RECORD +120 -0
  115. {skfolio-0.6.0.dist-info → skfolio-0.8.0.dist-info}/WHEEL +1 -1
  116. skfolio-0.6.0.dist-info/RECORD +0 -95
  117. {skfolio-0.6.0.dist-info → skfolio-0.8.0.dist-info/licenses}/LICENSE +0 -0
  118. {skfolio-0.6.0.dist-info → skfolio-0.8.0.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # Implementation derived from:
7
7
  # Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
@@ -18,6 +18,7 @@ import sklearn.base as skb
18
18
  import sklearn.model_selection as sks
19
19
  import sklearn.utils.metadata_routing as skm
20
20
  import sklearn.utils.parallel as skp
21
+ import sklearn.utils.validation as skv
21
22
 
22
23
  import skfolio.typing as skt
23
24
  from skfolio.cluster import HierarchicalClustering
@@ -311,9 +312,9 @@ class NestedClustersOptimization(BaseOptimization):
311
312
  w[cluster_ids] = fitted_inner_estimator.weights_
312
313
  inner_weights.append(w)
313
314
  inner_weights = np.array(inner_weights)
314
- assert not any(
315
- fitted_inner_estimators
316
- ), "fitted_inner_estimator iterator must be empty"
315
+ assert not any(fitted_inner_estimators), (
316
+ "fitted_inner_estimator iterator must be empty"
317
+ )
317
318
 
318
319
  # Outer cluster weights
319
320
  # To train the outer-estimator using the most data as possible, we use
@@ -355,10 +356,10 @@ class NestedClustersOptimization(BaseOptimization):
355
356
  # We validate and convert to numpy array only after inner-estimator fitting to
356
357
  # keep the assets names in case they are used in the estimator.
357
358
  if y is not None:
358
- X, y = self._validate_data(X, y)
359
+ X, y = skv.validate_data(self, X, y)
359
360
  y_pred = y[test_indices]
360
361
  else:
361
- X = self._validate_data(X)
362
+ X = skv.validate_data(self, X)
362
363
  y_pred = None
363
364
 
364
365
  X_pred = []
@@ -379,9 +380,9 @@ class NestedClustersOptimization(BaseOptimization):
379
380
  X_pred.append(np.asarray(pred))
380
381
  X_pred = np.array(X_pred).T
381
382
  if cv_predictions is None:
382
- assert not any(
383
- fitted_inner_estimators
384
- ), "fitted_inner_estimator iterator must be empty"
383
+ assert not any(fitted_inner_estimators), (
384
+ "fitted_inner_estimator iterator must be empty"
385
+ )
385
386
  else:
386
387
  assert not any(cv_predictions), "cv_predictions iterator must be empty"
387
388
 
@@ -1,3 +1,5 @@
1
+ """Hierarchical Optimization module."""
2
+
1
3
  from skfolio.optimization.cluster.hierarchical._base import (
2
4
  BaseHierarchicalOptimization,
3
5
  )
@@ -8,6 +10,6 @@ from skfolio.optimization.cluster.hierarchical._hrp import HierarchicalRiskParit
8
10
 
9
11
  __all__ = [
10
12
  "BaseHierarchicalOptimization",
11
- "HierarchicalRiskParity",
12
13
  "HierarchicalEqualRiskContribution",
14
+ "HierarchicalRiskParity",
13
15
  ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # Implementation derived from:
7
7
  # Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
@@ -349,7 +349,6 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
349
349
  max_weights : ndarray of shape (n_assets,)
350
350
  The weight upper bound 1D array.
351
351
  """
352
-
353
352
  if self.min_weights is None:
354
353
  min_weights = np.zeros(n_assets)
355
354
  else:
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # Weight constraints is a novel implementation, see docstring for more details.
7
7
 
8
8
  import numpy as np
@@ -10,6 +10,7 @@ import numpy.typing as npt
10
10
  import pandas as pd
11
11
  import scipy.cluster.hierarchy as sch
12
12
  import sklearn.utils.metadata_routing as skm
13
+ import sklearn.utils.validation as skv
13
14
 
14
15
  import skfolio.typing as skt
15
16
  from skfolio.cluster import HierarchicalClustering
@@ -346,7 +347,7 @@ class HierarchicalEqualRiskContribution(BaseHierarchicalOptimization):
346
347
  if self.risk_measure in [ExtraRiskMeasure.SKEW, ExtraRiskMeasure.KURTOSIS]:
347
348
  # Because Skew and Kurtosis can take negative values
348
349
  raise ValueError(
349
- f"risk_measure {self.risk_measure} currently not supported" f"in HERC"
350
+ f"risk_measure {self.risk_measure} currently not supported in HERC"
350
351
  )
351
352
 
352
353
  self.prior_estimator_ = check_estimator(
@@ -391,7 +392,7 @@ class HierarchicalEqualRiskContribution(BaseHierarchicalOptimization):
391
392
  labels = self.hierarchical_clustering_estimator_.labels_
392
393
  linkage_matrix = self.hierarchical_clustering_estimator_.linkage_matrix_
393
394
 
394
- X = self._validate_data(X)
395
+ X = skv.validate_data(self, X)
395
396
  n_assets = X.shape[1]
396
397
 
397
398
  min_weights, max_weights = self._convert_weights_bounds(n_assets=n_assets)
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # The risk measure generalization and constraint features are derived
7
7
  # from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
 
@@ -11,6 +11,7 @@ import numpy.typing as npt
11
11
  import pandas as pd
12
12
  import scipy.cluster.hierarchy as sch
13
13
  import sklearn.utils.metadata_routing as skm
14
+ import sklearn.utils.validation as skv
14
15
 
15
16
  import skfolio.typing as skt
16
17
  from skfolio.cluster import HierarchicalClustering
@@ -298,7 +299,7 @@ class HierarchicalRiskParity(BaseHierarchicalOptimization):
298
299
  if self.risk_measure in [ExtraRiskMeasure.SKEW, ExtraRiskMeasure.KURTOSIS]:
299
300
  # Because Skew and Kurtosis can take negative values
300
301
  raise ValueError(
301
- f"risk_measure {self.risk_measure} currently not supported" f"in HRP"
302
+ f"risk_measure {self.risk_measure} currently not supported in HRP"
302
303
  )
303
304
 
304
305
  self.prior_estimator_ = check_estimator(
@@ -339,7 +340,7 @@ class HierarchicalRiskParity(BaseHierarchicalOptimization):
339
340
  X=distance, y=None, **routed_params.hierarchical_clustering_estimator.fit
340
341
  )
341
342
 
342
- X = self._validate_data(X)
343
+ X = skv.validate_data(self, X)
343
344
  n_assets = X.shape[1]
344
345
 
345
346
  min_weights, max_weights = self._convert_weights_bounds(n_assets=n_assets)
@@ -1,3 +1,5 @@
1
+ """Convex Optimization module."""
2
+
1
3
  from skfolio.optimization.convex._base import ConvexOptimization, ObjectiveFunction
2
4
  from skfolio.optimization.convex._distributionally_robust import (
3
5
  DistributionallyRobustCVaR,
@@ -7,10 +9,10 @@ from skfolio.optimization.convex._mean_risk import MeanRisk
7
9
  from skfolio.optimization.convex._risk_budgeting import RiskBudgeting
8
10
 
9
11
  __all__ = [
10
- "ObjectiveFunction",
11
12
  "ConvexOptimization",
12
- "MeanRisk",
13
- "RiskBudgeting",
14
13
  "DistributionallyRobustCVaR",
15
14
  "MaximumDiversification",
15
+ "MeanRisk",
16
+ "ObjectiveFunction",
17
+ "RiskBudgeting",
16
18
  ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # The optimization features are derived
7
7
  # from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
 
@@ -144,7 +144,7 @@ class ConvexOptimization(BaseOptimization, ABC):
144
144
  all weights). `None` means no budget constraints.
145
145
  The default value is `1.0` (fully invested portfolio).
146
146
 
147
- Examples:
147
+ For example:
148
148
 
149
149
  * `budget = 1` --> fully invested portfolio.
150
150
  * `budget = 0` --> market neutral portfolio.
@@ -329,7 +329,7 @@ class ConvexOptimization(BaseOptimization, ABC):
329
329
  `groups` if the input `X` of the `fit` method is a DataFrame with these
330
330
  assets names in columns.
331
331
 
332
- Examples:
332
+ For example:
333
333
 
334
334
  * "SPX >= 0.10" --> SPX weight must be greater than 10% (note that you can also use `min_weights`)
335
335
  * "SX5E + TLT >= 0.2" --> the sum of SX5E and TLT weights must be greater than 20%
@@ -343,7 +343,7 @@ class ConvexOptimization(BaseOptimization, ABC):
343
343
  (asset name/asset groups) and the input `X` of the `fit` method must be a
344
344
  DataFrame with the assets names in columns.
345
345
 
346
- Examples:
346
+ For example:
347
347
 
348
348
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
349
349
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -657,7 +657,7 @@ class ConvexOptimization(BaseOptimization, ABC):
657
657
  )
658
658
 
659
659
  def _clear_models_cache(self):
660
- """CLear the cache of CVX models"""
660
+ """CLear the cache of CVX models."""
661
661
  self._cvx_cache = {}
662
662
 
663
663
  def _get_weight_constraints(
@@ -989,7 +989,7 @@ class ConvexOptimization(BaseOptimization, ABC):
989
989
  self._scale_constraints = cp.Constant(self.scale_constraints)
990
990
 
991
991
  def _get_custom_objective(self, w: cp.Variable) -> cp.Expression:
992
- """Returns the CVXPY expression evaluated by calling the `add_objective`
992
+ """Return the CVXPY expression evaluated by calling the `add_objective`
993
993
  function if provided, otherwise returns the CVXPY constant `0`.
994
994
 
995
995
  Parameters
@@ -1010,7 +1010,7 @@ class ConvexOptimization(BaseOptimization, ABC):
1010
1010
  )
1011
1011
 
1012
1012
  def _get_custom_constraints(self, w: cp.Variable) -> list[cp.Expression]:
1013
- """Returns the list of CVXPY expressions evaluated by calling the
1013
+ """Return the list of CVXPY expressions evaluated by calling the
1014
1014
  `add_constraint`s function if provided, otherwise returns an empty list.
1015
1015
 
1016
1016
  Parameters
@@ -1037,7 +1037,7 @@ class ConvexOptimization(BaseOptimization, ABC):
1037
1037
  def _cvx_expected_return(
1038
1038
  self, prior_model: PriorModel, w: cp.Variable
1039
1039
  ) -> cp.Expression:
1040
- """Expected Return expression"""
1040
+ """Expected Return expression."""
1041
1041
  if self.overwrite_expected_return is None:
1042
1042
  expected_return = prior_model.mu @ w
1043
1043
  else:
@@ -1685,7 +1685,8 @@ class ConvexOptimization(BaseOptimization, ABC):
1685
1685
  z2 = cp.vstack([w_reshaped, factor_reshaped])
1686
1686
 
1687
1687
  risk = covariance_uncertainty_set.k * cp.pnorm(
1688
- sc.linalg.sqrtm(covariance_uncertainty_set.sigma) @ (cp.vec(x) + cp.vec(y)),
1688
+ sc.linalg.sqrtm(covariance_uncertainty_set.sigma)
1689
+ @ (cp.vec(x, order="F") + cp.vec(y, order="F")),
1689
1690
  2,
1690
1691
  ) + cp.trace(prior_model.covariance @ (x + y))
1691
1692
  # semi-definite positive constraints
@@ -2,12 +2,13 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
 
7
7
  import cvxpy as cp
8
8
  import numpy as np
9
9
  import numpy.typing as npt
10
10
  import sklearn.utils.metadata_routing as skm
11
+ import sklearn.utils.validation as skv
11
12
 
12
13
  import skfolio.typing as skt
13
14
  from skfolio.measures import RiskMeasure
@@ -93,7 +94,7 @@ class DistributionallyRobustCVaR(ConvexOptimization):
93
94
  all weights). `None` means no budget constraints.
94
95
  The default value is `1.0` (fully invested portfolio).
95
96
 
96
- Examples:
97
+ For example:
97
98
 
98
99
  * `budget = 1` --> fully invested portfolio.
99
100
  * `budget = 0` --> market neutral portfolio.
@@ -133,7 +134,7 @@ class DistributionallyRobustCVaR(ConvexOptimization):
133
134
  `groups` if the input `X` of the `fit` method is a DataFrame with these
134
135
  assets names in columns.
135
136
 
136
- Examples:
137
+ For example:
137
138
 
138
139
  * "SPX >= 0.10" --> SPX weight must be greater than 10% (note that you can also use `min_weights`)
139
140
  * "SX5E + TLT >= 0.2" --> the sum of SX5E and TLT weights must be greater than 20%
@@ -147,7 +148,7 @@ class DistributionallyRobustCVaR(ConvexOptimization):
147
148
  (asset name/asset groups) and the input `X` of the `fit` method must be a
148
149
  DataFrame with the assets names in columns.
149
150
 
150
- Examples:
151
+ For example:
151
152
 
152
153
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
153
154
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -331,7 +332,9 @@ class DistributionallyRobustCVaR(ConvexOptimization):
331
332
  """
332
333
  routed_params = skm.process_routing(self, "fit", **fit_params)
333
334
 
334
- self._check_feature_names(X, reset=True)
335
+ # `X` is unchanged and only `feature_names_in_` is performed
336
+ _ = skv.validate_data(self, X, skip_check_array=True)
337
+
335
338
  # Used to avoid adding multiple times similar constrains linked to identical
336
339
  # risk models
337
340
  self.prior_estimator_ = check_estimator(
@@ -2,10 +2,11 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
9
+ import sklearn.utils.validation as skv
9
10
 
10
11
  import skfolio.typing as skt
11
12
  from skfolio.measures import RiskMeasure
@@ -76,7 +77,7 @@ class MaximumDiversification(MeanRisk):
76
77
  all weights). `None` means no budget constraints.
77
78
  The default value is `1.0` (fully invested portfolio).
78
79
 
79
- Examples:
80
+ For example:
80
81
 
81
82
  * `budget = 1` --> fully invested portfolio.
82
83
  * `budget = 0` --> market neutral portfolio.
@@ -209,7 +210,7 @@ class MaximumDiversification(MeanRisk):
209
210
  `groups` if the input `X` of the `fit` method is a DataFrame with these
210
211
  assets names in columns.
211
212
 
212
- Examples:
213
+ For example:
213
214
 
214
215
  * "SPX >= 0.10" --> SPX weight must be greater than 10% (note that you can also use `min_weights`)
215
216
  * "SX5E + TLT >= 0.2" --> the sum of SX5E and TLT weights must be greater than 20%
@@ -223,7 +224,7 @@ class MaximumDiversification(MeanRisk):
223
224
  (asset name/asset groups) and the input `X` of the `fit` method must be a
224
225
  DataFrame with the assets names in columns.
225
226
 
226
- Examples:
227
+ For example:
227
228
 
228
229
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
229
230
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -423,10 +424,11 @@ class MaximumDiversification(MeanRisk):
423
424
  self : MaximumDiversification
424
425
  Fitted estimator.
425
426
  """
426
- self._check_feature_names(X, reset=True)
427
+ # `X` is unchanged and only `feature_names_in_` is performed
428
+ _ = skv.validate_data(self, X, skip_check_array=True)
427
429
 
428
430
  def func(w, obj):
429
- """weighted volatilities"""
431
+ """Weighted volatilities."""
430
432
  covariance = obj.prior_estimator_.prior_model_.covariance
431
433
  return np.sqrt(np.diag(covariance)) @ w
432
434
 
@@ -4,7 +4,7 @@ import warnings
4
4
 
5
5
  # Copyright (c) 2023
6
6
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
7
- # License: BSD 3 clause
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
8
  # The optimization features are derived
9
9
  # from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
10
10
  import cvxpy as cp
@@ -13,6 +13,7 @@ import numpy.typing as npt
13
13
  import pandas as pd
14
14
  import sklearn as sk
15
15
  import sklearn.utils.metadata_routing as skm
16
+ import sklearn.utils.validation as skv
16
17
 
17
18
  import skfolio.typing as skt
18
19
  from skfolio.measures import RiskMeasure
@@ -193,7 +194,7 @@ class MeanRisk(ConvexOptimization):
193
194
  all weights). `None` means no budget constraints.
194
195
  The default value is `1.0` (fully invested portfolio).
195
196
 
196
- Examples:
197
+ For example:
197
198
 
198
199
  * `budget = 1` --> fully invested portfolio.
199
200
  * `budget = 0` --> market neutral portfolio.
@@ -378,7 +379,7 @@ class MeanRisk(ConvexOptimization):
378
379
  `groups` if the input `X` of the `fit` method is a DataFrame with these
379
380
  assets names in columns.
380
381
 
381
- Examples:
382
+ For example:
382
383
 
383
384
  * "SPX >= 0.10" --> SPX weight must be greater than 10% (note that you can also use `min_weights`)
384
385
  * "SX5E + TLT >= 0.2" --> the sum of SX5E and TLT weights must be greater than 20%
@@ -392,7 +393,7 @@ class MeanRisk(ConvexOptimization):
392
393
  (asset name/asset groups) and the input `X` of the `fit` method must be a
393
394
  DataFrame with the assets names in columns.
394
395
 
395
- Examples:
396
+ For example:
396
397
 
397
398
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
398
399
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -711,7 +712,7 @@ class MeanRisk(ConvexOptimization):
711
712
  self.max_gini_mean_difference = max_gini_mean_difference
712
713
 
713
714
  def _validation(self) -> None:
714
- """Validate the input parameters"""
715
+ """Validate the input parameters."""
715
716
  if not isinstance(self.risk_measure, RiskMeasure):
716
717
  raise TypeError("risk_measure must be of type `RiskMeasure`")
717
718
  if not isinstance(self.objective_function, ObjectiveFunction):
@@ -764,7 +765,9 @@ class MeanRisk(ConvexOptimization):
764
765
  """
765
766
  routed_params = skm.process_routing(self, "fit", **fit_params)
766
767
 
767
- self._check_feature_names(X, reset=True)
768
+ # `X` is unchanged and only `feature_names_in_` is performed
769
+ _ = skv.validate_data(self, X, skip_check_array=True)
770
+
768
771
  # Validate
769
772
  self._validation()
770
773
  # Used to avoid adding multiple times similar constrains linked to identical
@@ -893,7 +896,7 @@ class MeanRisk(ConvexOptimization):
893
896
  " 1d-array, a single-column DataFrame or a Series"
894
897
  )
895
898
  y = y[y.columns[0]]
896
- _, y = self._validate_data(X, y)
899
+ _, y = skv.validate_data(self, X, y)
897
900
  tracking_error = self._tracking_error(
898
901
  prior_model=prior_model, w=w, y=y, factor=factor
899
902
  )
@@ -1117,5 +1120,4 @@ def _optimal_homogenization_factor(mu: np.ndarray) -> float:
1117
1120
  value : float
1118
1121
  Homogenization factor.
1119
1122
  """
1120
-
1121
1123
  return min(1e3, max(1e-3, np.mean(np.abs(mu))))
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # The optimization features are derived
7
7
  # from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
 
@@ -10,6 +10,7 @@ import cvxpy as cp
10
10
  import numpy as np
11
11
  import numpy.typing as npt
12
12
  import sklearn.utils.metadata_routing as skm
13
+ import sklearn.utils.validation as skv
13
14
 
14
15
  import skfolio.typing as skt
15
16
  from skfolio.measures import RiskMeasure
@@ -225,7 +226,7 @@ class RiskBudgeting(ConvexOptimization):
225
226
  `groups` if the input `X` of the `fit` method is a DataFrame with these
226
227
  assets names in columns.
227
228
 
228
- Examples:
229
+ For example:
229
230
 
230
231
  * "SPX >= 0.10" --> SPX weight must be greater than 10% (note that you can also use `min_weights`)
231
232
  * "SX5E + TLT >= 0.2" --> the sum of SX5E and TLT weights must be greater than 20%
@@ -239,7 +240,7 @@ class RiskBudgeting(ConvexOptimization):
239
240
  (asset name/asset groups) and the input `X` of the `fit` method must be a
240
241
  DataFrame with the assets names in columns.
241
242
 
242
- Examples:
243
+ For example:
243
244
 
244
245
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
245
246
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -452,7 +453,8 @@ class RiskBudgeting(ConvexOptimization):
452
453
  """
453
454
  routed_params = skm.process_routing(self, "fit", **fit_params)
454
455
 
455
- self._check_feature_names(X, reset=True)
456
+ # `X` is unchanged and only `feature_names_in_` is performed
457
+ _ = skv.validate_data(self, X, skip_check_array=True)
456
458
 
457
459
  if not isinstance(self.risk_measure, RiskMeasure):
458
460
  raise TypeError("risk_measure must be of type `RiskMeasure`")
@@ -1,3 +1,5 @@
1
+ """Ensemble Optimization module."""
2
+
1
3
  from skfolio.optimization.ensemble._stacking import (
2
4
  BaseComposition,
3
5
  StackingOptimization,
@@ -1,10 +1,10 @@
1
1
  """Base Composition estimator.
2
- Follow same implementation as Base composition from sklearn
2
+ Follow same implementation as Base composition from sklearn.
3
3
  """
4
4
 
5
5
  # Copyright (c) 2023
6
6
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
7
- # License: BSD 3 clause
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
8
  # Implementation derived from:
9
9
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
10
10
  # Grisel Licensed under BSD 3 clause.
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
- # License: BSD 3 clause
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
6
  # Implementation derived from:
7
7
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
8
8
  # Grisel Licensed under BSD 3 clause.
@@ -330,9 +330,9 @@ class StackingOptimization(BaseOptimization, BaseComposition):
330
330
  # We validate and convert to numpy array only after base-estimator fitting
331
331
  # to keep the assets names in case they are used in the estimator.
332
332
  if y is not None:
333
- _, y = self._validate_data(X, y, multi_output=True)
333
+ _, y = skv.validate_data(self, X, y, multi_output=True)
334
334
  else:
335
- _ = self._validate_data(X)
335
+ _ = skv.validate_data(self, X)
336
336
 
337
337
  if isinstance(self.cv, BaseCombinatorialCV):
338
338
  X_pred = np.array(
@@ -1,3 +1,5 @@
1
+ """Naive Optimization module."""
2
+
1
3
  from skfolio.optimization.naive._naive import EqualWeighted, InverseVolatility, Random
2
4
 
3
- __all__ = ["InverseVolatility", "EqualWeighted", "Random"]
5
+ __all__ = ["EqualWeighted", "InverseVolatility", "Random"]
@@ -1,11 +1,12 @@
1
1
  """Naive estimators."""
2
2
 
3
3
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
- # License: BSD 3 clause
4
+ # SPDX-License-Identifier: BSD-3-Clause
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
8
  import sklearn.utils.metadata_routing as skm
9
+ import sklearn.utils.validation as skv
9
10
 
10
11
  from skfolio.optimization._base import BaseOptimization
11
12
  from skfolio.prior import BasePrior, EmpiricalPrior
@@ -141,7 +142,7 @@ class EqualWeighted(BaseOptimization):
141
142
  self : EqualWeighted
142
143
  Fitted estimator.
143
144
  """
144
- X = self._validate_data(X)
145
+ X = skv.validate_data(self, X)
145
146
  n_assets = X.shape[1]
146
147
  self.weights_ = np.ones(n_assets) / n_assets
147
148
  return self
@@ -185,7 +186,7 @@ class Random(BaseOptimization):
185
186
  self : EqualWeighted
186
187
  Fitted estimator.
187
188
  """
188
- X = self._validate_data(X)
189
+ X = skv.validate_data(self, X)
189
190
  n_assets = X.shape[1]
190
191
  self.weights_ = rand_weights_dirichlet(n=n_assets)
191
192
  return self
@@ -1,3 +1,5 @@
1
+ """Population module."""
2
+
1
3
  from skfolio.population._population import Population
2
4
 
3
5
  __all__ = ["Population"]