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.
Files changed (79) hide show
  1. skfolio/__init__.py +29 -0
  2. skfolio/cluster/__init__.py +8 -0
  3. skfolio/cluster/_hierarchical.py +387 -0
  4. skfolio/datasets/__init__.py +20 -0
  5. skfolio/datasets/_base.py +389 -0
  6. skfolio/datasets/data/__init__.py +0 -0
  7. skfolio/datasets/data/factors_dataset.csv.gz +0 -0
  8. skfolio/datasets/data/sp500_dataset.csv.gz +0 -0
  9. skfolio/datasets/data/sp500_index.csv.gz +0 -0
  10. skfolio/distance/__init__.py +26 -0
  11. skfolio/distance/_base.py +55 -0
  12. skfolio/distance/_distance.py +574 -0
  13. skfolio/exceptions.py +30 -0
  14. skfolio/measures/__init__.py +76 -0
  15. skfolio/measures/_enums.py +355 -0
  16. skfolio/measures/_measures.py +607 -0
  17. skfolio/metrics/__init__.py +3 -0
  18. skfolio/metrics/_scorer.py +121 -0
  19. skfolio/model_selection/__init__.py +18 -0
  20. skfolio/model_selection/_combinatorial.py +407 -0
  21. skfolio/model_selection/_validation.py +194 -0
  22. skfolio/model_selection/_walk_forward.py +221 -0
  23. skfolio/moments/__init__.py +41 -0
  24. skfolio/moments/covariance/__init__.py +29 -0
  25. skfolio/moments/covariance/_base.py +101 -0
  26. skfolio/moments/covariance/_covariance.py +1108 -0
  27. skfolio/moments/expected_returns/__init__.py +21 -0
  28. skfolio/moments/expected_returns/_base.py +31 -0
  29. skfolio/moments/expected_returns/_expected_returns.py +415 -0
  30. skfolio/optimization/__init__.py +36 -0
  31. skfolio/optimization/_base.py +147 -0
  32. skfolio/optimization/cluster/__init__.py +13 -0
  33. skfolio/optimization/cluster/_nco.py +348 -0
  34. skfolio/optimization/cluster/hierarchical/__init__.py +13 -0
  35. skfolio/optimization/cluster/hierarchical/_base.py +440 -0
  36. skfolio/optimization/cluster/hierarchical/_herc.py +406 -0
  37. skfolio/optimization/cluster/hierarchical/_hrp.py +368 -0
  38. skfolio/optimization/convex/__init__.py +16 -0
  39. skfolio/optimization/convex/_base.py +1944 -0
  40. skfolio/optimization/convex/_distributionally_robust.py +392 -0
  41. skfolio/optimization/convex/_maximum_diversification.py +417 -0
  42. skfolio/optimization/convex/_mean_risk.py +974 -0
  43. skfolio/optimization/convex/_risk_budgeting.py +560 -0
  44. skfolio/optimization/ensemble/__init__.py +6 -0
  45. skfolio/optimization/ensemble/_base.py +87 -0
  46. skfolio/optimization/ensemble/_stacking.py +326 -0
  47. skfolio/optimization/naive/__init__.py +3 -0
  48. skfolio/optimization/naive/_naive.py +173 -0
  49. skfolio/population/__init__.py +3 -0
  50. skfolio/population/_population.py +883 -0
  51. skfolio/portfolio/__init__.py +13 -0
  52. skfolio/portfolio/_base.py +1096 -0
  53. skfolio/portfolio/_multi_period_portfolio.py +610 -0
  54. skfolio/portfolio/_portfolio.py +842 -0
  55. skfolio/pre_selection/__init__.py +7 -0
  56. skfolio/pre_selection/_pre_selection.py +342 -0
  57. skfolio/preprocessing/__init__.py +3 -0
  58. skfolio/preprocessing/_returns.py +114 -0
  59. skfolio/prior/__init__.py +18 -0
  60. skfolio/prior/_base.py +63 -0
  61. skfolio/prior/_black_litterman.py +238 -0
  62. skfolio/prior/_empirical.py +163 -0
  63. skfolio/prior/_factor_model.py +268 -0
  64. skfolio/typing.py +50 -0
  65. skfolio/uncertainty_set/__init__.py +23 -0
  66. skfolio/uncertainty_set/_base.py +108 -0
  67. skfolio/uncertainty_set/_bootstrap.py +281 -0
  68. skfolio/uncertainty_set/_empirical.py +237 -0
  69. skfolio/utils/__init__.py +0 -0
  70. skfolio/utils/bootstrap.py +115 -0
  71. skfolio/utils/equations.py +350 -0
  72. skfolio/utils/sorting.py +117 -0
  73. skfolio/utils/stats.py +466 -0
  74. skfolio/utils/tools.py +567 -0
  75. skfolio-0.0.1.dist-info/LICENSE +29 -0
  76. skfolio-0.0.1.dist-info/METADATA +568 -0
  77. skfolio-0.0.1.dist-info/RECORD +79 -0
  78. skfolio-0.0.1.dist-info/WHEEL +5 -0
  79. skfolio-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,326 @@
1
+ """Stacking Optimization estimator."""
2
+
3
+ # Author: Hugo Delatte <delatte.hugo@gmail.com>
4
+ # License: BSD 3 clause
5
+
6
+ from copy import deepcopy
7
+
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+ import sklearn as sk
11
+ import sklearn.model_selection as skm
12
+ import sklearn.utils as sku
13
+ import sklearn.utils.parallel as skp
14
+ import sklearn.utils.validation as skv
15
+
16
+ import skfolio.typing as skt
17
+ from skfolio.measures import RatioMeasure
18
+ from skfolio.model_selection import BaseCombinatorialCV, cross_val_predict
19
+ from skfolio.optimization._base import BaseOptimization
20
+ from skfolio.optimization.convex import MeanRisk
21
+ from skfolio.optimization.ensemble._base import BaseComposition
22
+ from skfolio.utils.tools import check_estimator, fit_single_estimator
23
+
24
+
25
+ class StackingOptimization(BaseOptimization, BaseComposition):
26
+ """Stack of optimizations with a final optimization.
27
+
28
+ Stacking Optimization is an ensemble method that consists in stacking the output of
29
+ individual optimization estimators with a final optimization estimator.
30
+
31
+ The weights are the dot-product of individual estimators weights with the final
32
+ estimator weights. Stacking allows to use the strength of each individual estimator
33
+ by using their output as input of a final estimator.
34
+
35
+ To avoid data leakage, out-of-sample estimates are used to fit the outer
36
+ optimization.
37
+
38
+ Note that `estimators_` are fitted on the full `X` while `final_estimator_`
39
+ is trained using cross-validated predictions of the base estimators using
40
+ `cross_val_predict`.
41
+
42
+ Parameters
43
+ ----------
44
+ estimators : list[tuple[str, BaseOptimization]]
45
+ :ref:`Optimization estimators <optimization>` which will be stacked together.
46
+ Each element of the list is defined as a tuple of string (i.e. name) and an
47
+ :ref:`optimization estimator <optimization>`.
48
+
49
+ final_estimator : BaseOptimization, optional
50
+ A final :ref:`optimization estimator <optimization>` which will be used to
51
+ combine the base estimators.
52
+ The default (`None`) is to use :class:`~skfolio.optimization.MeanRisk`.
53
+
54
+ cv : BaseCrossValidator | BaseCombinatorialCV | int | "prefit" | "ignore", optional
55
+ Determines the cross-validation splitting strategy used in `cross_val_predict`
56
+ to train the `final_estimator`.
57
+ The default (`None`) is to use the 5-fold cross validation `KFold()`.
58
+ Possible inputs for `cv` are:
59
+
60
+ * "ignore": no cross-validation is used (note that it will likely lead to data leakage with a high risk of overfitting)
61
+ * integer, to specify the number of folds in a `KFold`
62
+ * An object to be used as a cross-validation generator
63
+ * An iterable yielding train, test splits
64
+ * "prefit" to assume the `estimators` are prefit, and skip cross validation
65
+ * A :class:`~skfolio.model_selection.CombinatorialPurgedCV`
66
+
67
+ If a `CombinatorialCV` cross-validator is used, each cluster out-of-sample
68
+ outputs becomes a collection of multiple paths instead of one single path. The
69
+ selected out-of-sample path among this collection of paths is chosen according
70
+ to the `quantile` and `quantile_measure` parameters.
71
+
72
+ If "prefit" is passed, it is assumed that all `estimators` have been fitted
73
+ already. The `final_estimator_` is trained on the `estimators` predictions on
74
+ the full training set and are **not** cross validated predictions.
75
+ Please note that if the models have been trained on the same data to train the
76
+ stacking model, there is a very high risk of overfitting.
77
+
78
+ n_jobs : int, optional
79
+ The number of jobs to run in parallel for `fit` of all `estimators`.
80
+ The value `-1` means using all processors.
81
+ The default (`None`) means 1 unless in a `joblib.parallel_backend` context.
82
+
83
+
84
+ quantile : float, default=0.5
85
+ Quantile for a given measure (`quantile_measure`) of the out-of-sample
86
+ inner-estimator paths when the `cv` parameter is a
87
+ :class:`~skfolio.model_selection.CombinatorialPurgedCV` cross-validator.
88
+ The default value is `0.5` corresponding to the path with the median measure.
89
+ (see `cv`)
90
+
91
+ quantile_measure : PerfMeasure or RatioMeasure or RiskMeasure or ExtraRiskMeasure, default=RatioMeasure.SHARPE_RATIO
92
+ Measure used for the quantile path selection (see `quantile` and `cv`).
93
+ The default is `RatioMeasure.SHARPE_RATIO`.
94
+
95
+ verbose : int, default=0
96
+ The verbosity level. The default value is `0`.
97
+
98
+ portfolio_params : dict, optional
99
+ Portfolio parameters passed to the portfolio evaluated by the `predict` and
100
+ `score` methods. If not provided, the `name` is copied from the optimization
101
+ model and systematically passed to the portfolio.
102
+
103
+ Attributes
104
+ ----------
105
+ weights_ : ndarray of shape (n_assets,)
106
+ Weights of the assets.
107
+
108
+ estimators_ : list[BaseOptimization]
109
+ The elements of the `estimators` parameter, having been fitted on the
110
+ training data. When `cv="prefit"`, `estimators_`
111
+ is set to `estimators` and is not fitted again.
112
+
113
+ named_estimators_ : dict[str, BaseOptimization]
114
+ Attribute to access any fitted sub-estimators by name.
115
+
116
+ final_estimator_ : BaseOptimization
117
+ The fitted `final_estimator`.
118
+
119
+ n_features_in_ : int
120
+ Number of assets seen during `fit`.
121
+
122
+ feature_names_in_ : ndarray of shape (`n_features_in_`,)
123
+ Names of assets seen during `fit`. Defined only when `X`
124
+ has assets names that are all strings.
125
+ """
126
+
127
+ estimators_: list[BaseOptimization]
128
+ final_estimator_: BaseOptimization
129
+ named_estimators_: dict[str, BaseOptimization]
130
+
131
+ def __init__(
132
+ self,
133
+ estimators: list[tuple[str, BaseOptimization]],
134
+ final_estimator: BaseOptimization | None = None,
135
+ cv: skm.BaseCrossValidator | BaseCombinatorialCV | str | int | None = None,
136
+ quantile: float = 0.5,
137
+ quantile_measure: skt.Measure = RatioMeasure.SHARPE_RATIO,
138
+ n_jobs: int | None = None,
139
+ verbose: int = 0,
140
+ portfolio_params: dict | None = None,
141
+ ):
142
+ super().__init__(portfolio_params=portfolio_params)
143
+ self.estimators = estimators
144
+ self.final_estimator = final_estimator
145
+ self.cv = cv
146
+ self.quantile = quantile
147
+ self.quantile_measure = quantile_measure
148
+ self.n_jobs = n_jobs
149
+ self.verbose = verbose
150
+
151
+ @property
152
+ def named_estimators(self):
153
+ """Dictionary to access any fitted sub-estimators by name.
154
+
155
+ Returns
156
+ -------
157
+ :class:`~sklearn.utils.Bunch`
158
+ """
159
+ return sku.Bunch(**dict(self.estimators))
160
+
161
+ def _validate_estimators(self) -> tuple[list[str], list[BaseOptimization]]:
162
+ """Validate the `estimators` parameter.
163
+
164
+ Returns
165
+ -------
166
+ names : list[str]
167
+ The list of estimators names.
168
+ estimators : list[BaseOptimization
169
+ The list of optimization estimators.
170
+ """
171
+ if self.estimators is None or len(self.estimators) == 0:
172
+ raise ValueError(
173
+ "Invalid 'estimators' attribute, 'estimators' should be a list"
174
+ " of (string, estimator) tuples."
175
+ )
176
+ names, estimators = zip(*self.estimators, strict=True)
177
+ # defined by MetaEstimatorMixin
178
+ self._validate_names(names)
179
+
180
+ return names, estimators
181
+
182
+ def set_params(self, **params):
183
+ """Set the parameters of an estimator from the ensemble.
184
+
185
+ Valid parameter keys can be listed with `get_params()`. Note that you
186
+ can directly set the parameters of the estimators contained in
187
+ `estimators`.
188
+
189
+ Parameters
190
+ ----------
191
+ **params : keyword arguments
192
+ Specific parameters using e.g.
193
+ `set_params(parameter_name=new_value)`. In addition, to setting the
194
+ parameters of the estimator, the individual estimator of the
195
+ estimators can also be set, or can be removed by setting them to
196
+ 'drop'.
197
+
198
+ Returns
199
+ -------
200
+ self : object
201
+ Estimator instance.
202
+ """
203
+ super()._set_params("estimators", **params)
204
+ return self
205
+
206
+ def get_params(self, deep=True):
207
+ """Get the parameters of an estimator from the ensemble.
208
+
209
+ Returns the parameters given in the constructor as well as the
210
+ estimators contained within the `estimators` parameter.
211
+
212
+ Parameters
213
+ ----------
214
+ deep : bool, default=True
215
+ Setting it to True gets the various estimators and the parameters
216
+ of the estimators as well.
217
+
218
+ Returns
219
+ -------
220
+ params : dict
221
+ Parameter and estimator names mapped to their values or parameter
222
+ names mapped to their values.
223
+ """
224
+ return super()._get_params("estimators", deep=deep)
225
+
226
+ def fit(
227
+ self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
228
+ ) -> "StackingOptimization":
229
+ """Fit the Stacking Optimization estimator.
230
+
231
+ Parameters
232
+ ----------
233
+ X : array-like of shape (n_observations, n_assets)
234
+ Price returns of the assets.
235
+
236
+ y : array-like of shape (n_observations, n_targets), optional
237
+ Price returns of factors or a target benchmark.
238
+ The default is `None`.
239
+
240
+ Returns
241
+ -------
242
+ self : StackingOptimization
243
+ Fitted estimator.
244
+ """
245
+ names, all_estimators = self._validate_estimators()
246
+ self.final_estimator_ = check_estimator(
247
+ self.final_estimator,
248
+ default=MeanRisk(),
249
+ check_type=BaseOptimization,
250
+ )
251
+
252
+ if self.cv == "prefit":
253
+ self.estimators_ = []
254
+ for estimator in all_estimators:
255
+ skv.check_is_fitted(estimator)
256
+ self.estimators_.append(estimator)
257
+ else:
258
+ # Fit the base estimators on the whole training data. Those
259
+ # base estimators will be used to retrieve the inner weights.
260
+ # They are exposed publicly.
261
+ # noinspection PyCallingNonCallable
262
+ self.estimators_ = skp.Parallel(n_jobs=self.n_jobs)(
263
+ skp.delayed(fit_single_estimator)(sk.clone(est), X, y)
264
+ for est in all_estimators
265
+ )
266
+
267
+ self.named_estimators_ = {
268
+ name: estimator
269
+ for name, estimator in zip(names, self.estimators_, strict=True)
270
+ }
271
+
272
+ inner_weights = np.array([estimator.weights_ for estimator in self.estimators_])
273
+
274
+ # To train the final-estimator using the most data as possible, we use
275
+ # a cross-validation to obtain the output of the stacked estimators.
276
+ # To ensure that the data provided to each estimator are the same,
277
+ # we need to set the random state of the cv if there is one and we
278
+ # need to take a copy.
279
+ if self.cv in ["prefit", "ignore"]:
280
+ X_pred = np.array(
281
+ [estimator.predict(X) for estimator in self.estimators_]
282
+ ).T
283
+ else:
284
+ cv = skm.check_cv(self.cv)
285
+ if hasattr(cv, "random_state") and cv.random_state is None:
286
+ cv.random_state = np.random.RandomState()
287
+ # noinspection PyCallingNonCallable
288
+ cv_predictions = skp.Parallel(n_jobs=self.n_jobs)(
289
+ skp.delayed(cross_val_predict)(
290
+ sk.clone(estimator),
291
+ X,
292
+ y,
293
+ cv=deepcopy(cv),
294
+ method="predict",
295
+ n_jobs=self.n_jobs,
296
+ verbose=self.verbose,
297
+ )
298
+ for estimator in all_estimators
299
+ )
300
+
301
+ # We validate and convert to numpy array only after base-estimator fitting
302
+ # to keep the assets names in case they are used in the estimator.
303
+ if y is not None:
304
+ _, y = self._validate_data(X, y, multi_output=True)
305
+ else:
306
+ _ = self._validate_data(X)
307
+
308
+ if isinstance(self.cv, BaseCombinatorialCV):
309
+ X_pred = np.array(
310
+ [
311
+ pred.quantile(measure=self.quantile_measure, q=self.quantile)
312
+ for pred in cv_predictions
313
+ ]
314
+ ).T
315
+ else:
316
+ X_pred = np.array(cv_predictions).T
317
+ if y is not None:
318
+ test_indices = np.sort(
319
+ np.concatenate([test for _, test in cv.split(X, y)])
320
+ )
321
+ y = y[test_indices]
322
+
323
+ fit_single_estimator(self.final_estimator_, X_pred, y)
324
+ outer_weights = self.final_estimator_.weights_
325
+ self.weights_ = outer_weights @ inner_weights
326
+ return self
@@ -0,0 +1,3 @@
1
+ from skfolio.optimization.naive._naive import EqualWeighted, InverseVolatility, Random
2
+
3
+ __all__ = ["InverseVolatility", "EqualWeighted", "Random"]
@@ -0,0 +1,173 @@
1
+ """Naive 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
+
9
+ from skfolio.optimization._base import BaseOptimization
10
+ from skfolio.prior import BasePrior, EmpiricalPrior
11
+ from skfolio.utils.stats import rand_weights_dirichlet
12
+ from skfolio.utils.tools import check_estimator
13
+
14
+
15
+ class InverseVolatility(BaseOptimization):
16
+ """Inverse Volatility estimator.
17
+
18
+ Each asset weight is computed using the inverse of its volatility and rescaled to
19
+ have a sum of weights equal to one. The assets volatilities are derived from the
20
+ prior estimator's covariance matrix.
21
+
22
+ Parameters
23
+ ----------
24
+ prior_estimator : BasePrior, optional
25
+ :ref:`Prior estimator <prior>`.
26
+ The prior estimator is used to estimate the :class:`~skfolio.prior.PriorModel`
27
+ containing the estimation of assets expected returns, covariance matrix,
28
+ returns and Cholesky decomposition of the covariance.
29
+ The default (`None`) is to use :class:`~skfolio.prior.EmpiricalPrior`.
30
+
31
+ portfolio_params : dict, optional
32
+ Portfolio parameters passed to the portfolio evaluated by the `predict` and
33
+ `score` methods. If not provided, the `name`, `transaction_costs`,
34
+ `management_fees` and `previous_weights` are copied from the optimization
35
+ model and systematically passed to the portfolio.
36
+
37
+ Attributes
38
+ ----------
39
+ weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets)
40
+ Weights of the assets.
41
+
42
+ prior_estimator_ : BasePrior
43
+ Fitted `prior_estimator`.
44
+ """
45
+
46
+ prior_estimator_: BasePrior
47
+
48
+ def __init__(
49
+ self,
50
+ prior_estimator: BasePrior | None = None,
51
+ portfolio_params: dict | None = None,
52
+ ):
53
+ super().__init__(portfolio_params=portfolio_params)
54
+ self.prior_estimator = prior_estimator
55
+
56
+ def fit(
57
+ self, X: npt.ArrayLike, y: npt.ArrayLike | None = None
58
+ ) -> "InverseVolatility":
59
+ """Fit the Inverse Volatility estimator.
60
+
61
+ Parameters
62
+ ----------
63
+ X : array-like of shape (n_observations, n_assets)
64
+ Price returns of the assets.
65
+
66
+ y : array-like of shape (n_observations, n_targets), optional
67
+ Price returns of factors or a target benchmark.
68
+ The default is `None`.
69
+
70
+ Returns
71
+ -------
72
+ self : InverseVolatility
73
+ Fitted estimator.
74
+ """
75
+ # fitting prior estimator
76
+ self.prior_estimator_ = check_estimator(
77
+ self.prior_estimator,
78
+ default=EmpiricalPrior(),
79
+ check_type=BasePrior,
80
+ )
81
+ self.prior_estimator_.fit(X, y)
82
+ covariance = self.prior_estimator_.prior_model_.covariance
83
+ w = 1 / np.sqrt(np.diag(covariance))
84
+ self.weights_ = w / sum(w)
85
+ return self
86
+
87
+
88
+ class EqualWeighted(BaseOptimization):
89
+ """Equally Weighted estimator.
90
+
91
+ Each asset weight is equal to `1/n_assets`.
92
+
93
+ Parameters
94
+ ----------
95
+ portfolio_params : dict, optional
96
+ Portfolio parameters passed to the portfolio evaluated by the `predict` and
97
+ `score` methods. If not provided, the `name`, `transaction_costs`,
98
+ `management_fees` and `previous_weights` are copied from the optimization
99
+ model and systematically passed to the portfolio.
100
+
101
+ Attributes
102
+ ----------
103
+ weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets)
104
+ Weights of the assets.
105
+ """
106
+
107
+ def __init__(self, portfolio_params: dict | None = None):
108
+ super().__init__(portfolio_params=portfolio_params)
109
+
110
+ def fit(self, X: npt.ArrayLike, y=None) -> "EqualWeighted":
111
+ """Fit the Equal Weighted estimator.
112
+
113
+ Parameters
114
+ ----------
115
+ X : array-like of shape (n_observations, n_assets)
116
+ Price returns of the assets.
117
+
118
+ y : Ignored
119
+ Not used, present for API consistency by convention.
120
+
121
+ Returns
122
+ -------
123
+ self : EqualWeighted
124
+ Fitted estimator.
125
+ """
126
+ X = self._validate_data(X)
127
+ n_assets = X.shape[1]
128
+ self.weights_ = np.ones(n_assets) / n_assets
129
+ return self
130
+
131
+
132
+ class Random(BaseOptimization):
133
+ """Random weight estimator.
134
+
135
+ The assets weight are drawn from a Dirichlet distribution and sum to one.
136
+
137
+ Parameters
138
+ ----------
139
+ portfolio_params : dict, optional
140
+ Portfolio parameters passed to the portfolio evaluated by the `predict` and
141
+ `score` methods. If not provided, the `name`, `transaction_costs`,
142
+ `management_fees` and `previous_weights` are copied from the optimization
143
+ model and systematically passed to the portfolio.
144
+
145
+ Attributes
146
+ ----------
147
+ weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets)
148
+ Weights of the assets.
149
+ """
150
+
151
+ def __init__(self, portfolio_params: dict | None = None):
152
+ super().__init__(portfolio_params=portfolio_params)
153
+
154
+ def fit(self, X: npt.ArrayLike, y=None):
155
+ """Fit the Random Weighted estimator.
156
+
157
+ Parameters
158
+ ----------
159
+ X : array-like of shape (n_observations, n_assets)
160
+ Price returns of the assets.
161
+
162
+ y : Ignored
163
+ Not used, present for API consistency by convention.
164
+
165
+ Returns
166
+ -------
167
+ self : EqualWeighted
168
+ Fitted estimator.
169
+ """
170
+ X = self._validate_data(X)
171
+ n_assets = X.shape[1]
172
+ self.weights_ = rand_weights_dirichlet(n=n_assets)
173
+ return self
@@ -0,0 +1,3 @@
1
+ from skfolio.population._population import Population
2
+
3
+ __all__ = ["Population"]