skfolio 0.1.2__py3-none-any.whl → 0.2.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.
skfolio/datasets/_base.py CHANGED
@@ -140,7 +140,10 @@ def download_dataset(
140
140
  DataFrame with each row representing one observation and each column
141
141
  representing the asset price of a given observation.
142
142
  """
143
- url = f"https://github.com/skfolio/skfolio/raw/main/datasets/{data_filename}.csv.gz"
143
+ url = (
144
+ f"https://github.com/skfolio/skfolio-datasets/raw/main/"
145
+ f"datasets/{data_filename}.csv.gz"
146
+ )
144
147
 
145
148
  data_home = get_data_home(data_home=data_home)
146
149
  filepath = os.path.join(data_home, f"{data_filename}.pkz")
@@ -4,7 +4,6 @@
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
5
  # License: BSD 3 clause
6
6
 
7
-
8
7
  import numpy as np
9
8
  import numpy.typing as npt
10
9
  import pandas as pd
@@ -19,7 +19,9 @@ class BaseMeasure(AutoEnum):
19
19
  (
20
20
  word.capitalize()
21
21
  if len(word) > 3
22
- else word.upper() if len(word) != 2 else word.lower()
22
+ else word.upper()
23
+ if len(word) != 2
24
+ else word.lower()
23
25
  )
24
26
  for word in self.value.split("_")
25
27
  ]
@@ -6,7 +6,6 @@
6
6
  # Gini mean difference and OWA GMD weights features are derived
7
7
  # from Riskfolio-Lib, Copyright (c) 2020-2023, Dany Cajas, Licensed under BSD 3 clause.
8
8
 
9
-
10
9
  import numpy as np
11
10
  import scipy.optimize as sco
12
11
 
@@ -6,6 +6,7 @@
6
6
  from skfolio.model_selection._combinatorial import (
7
7
  BaseCombinatorialCV,
8
8
  CombinatorialPurgedCV,
9
+ optimal_folds_number,
9
10
  )
10
11
  from skfolio.model_selection._validation import cross_val_predict
11
12
  from skfolio.model_selection._walk_forward import WalkForward
@@ -15,4 +16,5 @@ __all__ = [
15
16
  "WalkForward",
16
17
  "BaseCombinatorialCV",
17
18
  "CombinatorialPurgedCV",
19
+ "optimal_folds_number",
18
20
  ]
@@ -197,19 +197,13 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
197
197
  @property
198
198
  def n_splits(self) -> int:
199
199
  """Number of splits"""
200
- return int(
201
- math.factorial(self.n_folds)
202
- / (
203
- math.factorial(self.n_test_folds)
204
- * math.factorial(self.n_folds - self.n_test_folds)
205
- )
206
- )
200
+ return _n_splits(n_folds=self.n_folds, n_test_folds=self.n_test_folds)
207
201
 
208
202
  @property
209
203
  def n_test_paths(self) -> int:
210
204
  """Number of test paths that can be reconstructed from the train/test
211
205
  combinations"""
212
- return self.n_splits * self.n_test_folds // self.n_folds
206
+ return _n_test_paths(n_folds=self.n_folds, n_test_folds=self.n_test_folds)
213
207
 
214
208
  @property
215
209
  def test_set_index(self) -> np.ndarray:
@@ -320,19 +314,24 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
320
314
  yield train_index, test_index_list
321
315
 
322
316
  def summary(self, X) -> pd.Series:
323
- n_samples = X.shape[0]
324
- return pd.Series({
325
- "Number of Observations": n_samples,
326
- "Total Number of Folds": self.n_folds,
327
- "Number of Test Folds": self.n_test_folds,
328
- "Purge Size": self.purged_size,
329
- "Embargo Size": self.embargo_size,
330
- "Average Training Size": int(
331
- n_samples / self.n_folds * (self.n_folds - self.n_test_folds)
332
- ),
333
- "Number of Test Paths": self.n_test_paths,
334
- "Number of Training Combinations": self.n_splits,
335
- })
317
+ n_observations = X.shape[0]
318
+ avg_train_size = _avg_train_size(
319
+ n_observations=n_observations,
320
+ n_folds=self.n_folds,
321
+ n_test_folds=self.n_test_folds,
322
+ )
323
+ return pd.Series(
324
+ {
325
+ "Number of Observations": n_observations,
326
+ "Total Number of Folds": self.n_folds,
327
+ "Number of Test Folds": self.n_test_folds,
328
+ "Purge Size": self.purged_size,
329
+ "Embargo Size": self.embargo_size,
330
+ "Average Training Size": int(avg_train_size),
331
+ "Number of Test Paths": self.n_test_paths,
332
+ "Number of Training Combinations": self.n_splits,
333
+ }
334
+ )
336
335
 
337
336
  def plot_train_test_folds(self) -> skt.Figure:
338
337
  """Plot the train/test fold locations"""
@@ -408,3 +407,155 @@ class CombinatorialPurgedCV(BaseCombinatorialCV):
408
407
  )
409
408
 
410
409
  return fig
410
+
411
+
412
+ def _n_splits(n_folds: int, n_test_folds: int) -> int:
413
+ """Number of splits.
414
+
415
+ Parameters
416
+ ----------
417
+ n_folds : int
418
+ Number of folds.
419
+
420
+ n_test_folds : int
421
+ Number of test folds.
422
+
423
+ Returns
424
+ -------
425
+ n_splits : int
426
+ Number of splits
427
+ """
428
+ return int(math.comb(n_folds, n_test_folds))
429
+
430
+
431
+ def _n_test_paths(n_folds: int, n_test_folds: int) -> int:
432
+ """Number of test paths that can be reconstructed from the train/test
433
+ combinations
434
+
435
+ Parameters
436
+ ----------
437
+ n_folds : int
438
+ Number of folds.
439
+
440
+ n_test_folds : int
441
+ Number of test folds.
442
+
443
+ Returns
444
+ -------
445
+ n_splits : int
446
+ Number of test paths.
447
+ """
448
+ return (
449
+ _n_splits(n_folds=n_folds, n_test_folds=n_test_folds) * n_test_folds // n_folds
450
+ )
451
+
452
+
453
+ def _avg_train_size(n_observations: int, n_folds: int, n_test_folds: int) -> float:
454
+ """Average number of observations contained in each training set.
455
+
456
+ Parameters
457
+ ----------
458
+ n_observations : int
459
+ Number of observations.
460
+
461
+ n_folds : int
462
+ Number of folds.
463
+
464
+ n_test_folds : int
465
+ Number of test folds.
466
+
467
+ Returns
468
+ -------
469
+ avg_train_size : float
470
+ Average number of observations contained in each training set.
471
+ """
472
+ return n_observations / n_folds * (n_folds - n_test_folds)
473
+
474
+
475
+ def optimal_folds_number(
476
+ n_observations: int,
477
+ target_train_size: int,
478
+ target_n_test_paths: int,
479
+ weight_train_size: float = 1,
480
+ weight_n_test_paths: float = 1,
481
+ ) -> tuple[int, int]:
482
+ r"""Find the optimal number of folds (total folds and test folds) for a target
483
+ training size and a target number of test paths.
484
+
485
+ We find `x = n_folds` and `y = n_test_folds` that minimizes the below
486
+ cost function of the relative distance from the two targets:
487
+
488
+ .. math::
489
+ cost(x,y) = w_{f} \times \lvert\frac{f(x,y)-f_{target}}{f_{target}}\rvert + w_{g} \times \lvert\frac{g(x,y)-g_{target}}{g_{target}}\rvert
490
+
491
+ with :math:`w_{f}` and :math:`w_{g}` the weights assigned to the distance
492
+ from each target and :math:`f(x,y)` and :math:`g(x,y)` the average training size
493
+ and the number of test paths as a function of the number of total folds and test
494
+ folds.
495
+
496
+ This is a combinatorial problem with :math:`\frac{T\times(T-3)}{2}` combinations,
497
+ with :math:`T` the number of observations.
498
+
499
+ We reduce the search space by using the combinatorial symetry
500
+ :math:`{n \choose k}={n \choose n-k}` and skipping cost computation above 1e5.
501
+
502
+ Parameters
503
+ ----------
504
+ n_observations : int
505
+ Number of observations.
506
+
507
+ target_train_size : int
508
+ The target number of observation in the training set.
509
+
510
+ target_n_test_paths : int
511
+ The target number of test paths (that can be reconstructed from the train/test
512
+ combinations).
513
+
514
+ weight_train_size : float, default=1
515
+ The weight assigned to the distance from the target train size.
516
+ The default value is 1.
517
+
518
+ weight_n_test_paths : float, default=1
519
+ The weight assigned to the distance from the target number of test paths.
520
+ The default value is 1.
521
+
522
+ Returns
523
+ -------
524
+ n_folds : int
525
+ Optimal number of total folds.
526
+
527
+ n_test_folds : int
528
+ Optimal number of test folds.
529
+ """
530
+
531
+ def _cost(
532
+ x: int,
533
+ y: int,
534
+ ) -> float:
535
+ n_test_paths = _n_test_paths(n_folds=x, n_test_folds=y)
536
+ avg_train_size = _avg_train_size(
537
+ n_observations=n_observations, n_folds=x, n_test_folds=y
538
+ )
539
+ return (
540
+ weight_n_test_paths
541
+ * abs(n_test_paths - target_n_test_paths)
542
+ / target_n_test_paths
543
+ + weight_train_size
544
+ * abs(avg_train_size - target_train_size)
545
+ / target_train_size
546
+ )
547
+
548
+ costs = []
549
+ res = []
550
+ for n_folds in range(3, n_observations + 1):
551
+ i = None
552
+ for n_test_folds in range(2, n_folds):
553
+ if i is None or n_folds - n_test_folds <= i:
554
+ cost = _cost(x=n_folds, y=n_test_folds)
555
+ costs.append(cost)
556
+ res.append((n_folds, n_test_folds))
557
+ if i is None and cost > 1e5:
558
+ i = n_test_folds
559
+
560
+ j = np.argmin(costs)
561
+ return res[j]
@@ -170,12 +170,14 @@ def cross_val_predict(
170
170
  path_id = path_ids[i, j]
171
171
  portfolios[path_id].append(p)
172
172
  name = portfolio_params.pop("name", "path")
173
- pred = Population([
174
- MultiPeriodPortfolio(
175
- name=f"{name}_{i}", portfolios=portfolios[i], **portfolio_params
176
- )
177
- for i in range(path_nb)
178
- ])
173
+ pred = Population(
174
+ [
175
+ MultiPeriodPortfolio(
176
+ name=f"{name}_{i}", portfolios=portfolios[i], **portfolio_params
177
+ )
178
+ for i in range(path_nb)
179
+ ]
180
+ )
179
181
  else:
180
182
  # We need to re-order the test folds in case they were un-ordered by the
181
183
  # CV generator.
@@ -29,8 +29,8 @@ class BaseOptimization(skb.BaseEstimator, ABC):
29
29
  portfolio_params : dict, optional
30
30
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
31
31
  `score` methods. If not provided, the `name`, `transaction_costs`,
32
- `management_fees` and `previous_weights` are copied from the optimization
33
- model and systematically passed to the portfolio.
32
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
33
+ optimization model and passed to the portfolio.
34
34
 
35
35
  Attributes
36
36
  ----------
@@ -84,7 +84,12 @@ class BaseOptimization(skb.BaseEstimator, ABC):
84
84
  ptf_kwargs = self.portfolio_params.copy()
85
85
 
86
86
  # Set the default portfolio parameters equal to the optimization parameters
87
- for param in ["transaction_costs", "management_fees", "previous_weights"]:
87
+ for param in [
88
+ "transaction_costs",
89
+ "management_fees",
90
+ "previous_weights",
91
+ "risk_free_rate",
92
+ ]:
88
93
  if param not in ptf_kwargs and hasattr(self, param):
89
94
  ptf_kwargs[param] = getattr(self, param)
90
95
 
@@ -97,15 +102,17 @@ class BaseOptimization(skb.BaseEstimator, ABC):
97
102
  # For a 2D array we return a population of portfolios.
98
103
  if self.weights_.ndim == 2:
99
104
  n_portfolios = self.weights_.shape[0]
100
- return Population([
101
- Portfolio(
102
- X=X,
103
- weights=self.weights_[i],
104
- name=f"ptf{i} - {name}",
105
- **ptf_kwargs,
106
- )
107
- for i in range(n_portfolios)
108
- ])
105
+ return Population(
106
+ [
107
+ Portfolio(
108
+ X=X,
109
+ weights=self.weights_[i],
110
+ name=f"ptf{i} - {name}",
111
+ **ptf_kwargs,
112
+ )
113
+ for i in range(n_portfolios)
114
+ ]
115
+ )
109
116
  return Portfolio(X=X, weights=self.weights_, name=name, **ptf_kwargs)
110
117
 
111
118
  def score(self, X: npt.ArrayLike, y: npt.ArrayLike = None) -> float:
@@ -8,6 +8,7 @@
8
8
  # scikit-learn, Copyright (c) 2007-2010 David Cournapeau, Fabian Pedregosa, Olivier
9
9
 
10
10
  from abc import ABC, abstractmethod
11
+ from typing import Any
11
12
 
12
13
  import numpy as np
13
14
  import numpy.typing as npt
@@ -183,8 +184,8 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
183
184
  portfolio_params : dict, optional
184
185
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
185
186
  `score` methods. If not provided, the `name`, `transaction_costs`,
186
- `management_fees` and `previous_weights` are copied from the optimization
187
- model and systematically passed to the portfolio.
187
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
188
+ optimization model and passed to the portfolio.
188
189
 
189
190
  Attributes
190
191
  ----------
@@ -235,7 +236,7 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
235
236
  self,
236
237
  value: float | dict | np.ndarray | list,
237
238
  n_assets: int,
238
- fill_value: any,
239
+ fill_value: Any,
239
240
  name: str,
240
241
  ) -> np.ndarray:
241
242
  """Convert input to cleaned 1D array
@@ -250,7 +251,7 @@ class BaseHierarchicalOptimization(BaseOptimization, ABC):
250
251
  n_assets : int
251
252
  Number of assets. Used to verify the shape of the converted array.
252
253
 
253
- fill_value : any
254
+ fill_value : Any
254
255
  When `items` is a dictionary, elements that are not in `asset_names` are
255
256
  filled with `fill_value` in the converted array.
256
257
 
@@ -204,8 +204,8 @@ class HierarchicalEqualRiskContribution(BaseHierarchicalOptimization):
204
204
  portfolio_params : dict, optional
205
205
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
206
206
  `score` methods. If not provided, the `name`, `transaction_costs`,
207
- `management_fees` and `previous_weights` are copied from the optimization
208
- model and systematically passed to the portfolio.
207
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
208
+ optimization model and passed to the portfolio.
209
209
 
210
210
  Attributes
211
211
  ----------
@@ -6,7 +6,6 @@
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
 
9
-
10
9
  import numpy as np
11
10
  import numpy.typing as npt
12
11
  import pandas as pd
@@ -205,8 +204,8 @@ class HierarchicalRiskParity(BaseHierarchicalOptimization):
205
204
  portfolio_params : dict, optional
206
205
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
207
206
  `score` methods. If not provided, the `name`, `transaction_costs`,
208
- `management_fees` and `previous_weights` are copied from the optimization
209
- model and systematically passed to the portfolio.
207
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
208
+ optimization model and passed to the portfolio.
210
209
 
211
210
  Attributes
212
211
  ----------
@@ -9,6 +9,7 @@
9
9
  import warnings
10
10
  from abc import ABC, abstractmethod
11
11
  from enum import auto
12
+ from typing import Any
12
13
 
13
14
  import cvxpy as cp
14
15
  import cvxpy.constraints.constraint as cpc
@@ -403,8 +404,8 @@ class ConvexOptimization(BaseOptimization, ABC):
403
404
  portfolio_params : dict, optional
404
405
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
405
406
  `score` methods. If not provided, the `name`, `transaction_costs`,
406
- `management_fees` and `previous_weights` are copied from the optimization
407
- model and systematically passed to the portfolio.
407
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
408
+ optimization model and passed to the portfolio.
408
409
 
409
410
  Attributes
410
411
  ----------
@@ -575,7 +576,7 @@ class ConvexOptimization(BaseOptimization, ABC):
575
576
  self,
576
577
  value: float | dict | npt.ArrayLike | None,
577
578
  n_assets: int,
578
- fill_value: any,
579
+ fill_value: Any,
579
580
  name: str,
580
581
  ) -> float | np.ndarray:
581
582
  """Convert input to cleaned float or ndarray.
@@ -588,7 +589,7 @@ class ConvexOptimization(BaseOptimization, ABC):
588
589
  n_assets : int
589
590
  Number of assets. Used to verify the shape of the converted array.
590
591
 
591
- fill_value : any
592
+ fill_value : Any
592
593
  When `items` is a dictionary, elements that are not in `asset_names` are
593
594
  filled with `fill_value` in the converted array.
594
595
 
@@ -208,8 +208,8 @@ class DistributionallyRobustCVaR(ConvexOptimization):
208
208
  portfolio_params : dict, optional
209
209
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
210
210
  `score` methods. If not provided, the `name`, `transaction_costs`,
211
- `management_fees` and `previous_weights` are copied from the optimization
212
- model and systematically passed to the portfolio.
211
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
212
+ optimization model and passed to the portfolio.
213
213
 
214
214
  Attributes
215
215
  ----------
@@ -303,8 +303,8 @@ class MaximumDiversification(MeanRisk):
303
303
  portfolio_params : dict, optional
304
304
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
305
305
  `score` methods. If not provided, the `name`, `transaction_costs`,
306
- `management_fees` and `previous_weights` are copied from the optimization
307
- model and systematically passed to the portfolio.
306
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
307
+ optimization model and passed to the portfolio.
308
308
 
309
309
  Attributes
310
310
  ----------
@@ -510,8 +510,8 @@ class MeanRisk(ConvexOptimization):
510
510
  portfolio_params : dict, optional
511
511
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
512
512
  `score` methods. If not provided, the `name`, `transaction_costs`,
513
- `management_fees` and `previous_weights` are copied from the optimization
514
- model and systematically passed to the portfolio.
513
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
514
+ optimization model and passed to the portfolio.
515
515
 
516
516
  Attributes
517
517
  ----------
@@ -340,8 +340,8 @@ class RiskBudgeting(ConvexOptimization):
340
340
  portfolio_params : dict, optional
341
341
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
342
342
  `score` methods. If not provided, the `name`, `transaction_costs`,
343
- `management_fees` and `previous_weights` are copied from the optimization
344
- model and systematically passed to the portfolio.
343
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
344
+ optimization model and passed to the portfolio.
345
345
 
346
346
  Attributes
347
347
  ----------
@@ -80,9 +80,7 @@ class BaseComposition(skb.BaseEstimator, ABC):
80
80
  invalid_names = set(names).intersection(self.get_params(deep=False))
81
81
  if invalid_names:
82
82
  raise ValueError(
83
- "Estimator names conflict with constructor arguments: {!r}".format(
84
- sorted(invalid_names)
85
- )
83
+ f"Estimator names conflict with constructor arguments: {sorted(invalid_names)!r}"
86
84
  )
87
85
  invalid_names = [name for name in names if "__" in name]
88
86
  if invalid_names:
@@ -312,10 +312,12 @@ class StackingOptimization(BaseOptimization, BaseComposition):
312
312
  _ = self._validate_data(X)
313
313
 
314
314
  if isinstance(self.cv, BaseCombinatorialCV):
315
- X_pred = np.array([
316
- pred.quantile(measure=self.quantile_measure, q=self.quantile)
317
- for pred in cv_predictions
318
- ]).T
315
+ X_pred = np.array(
316
+ [
317
+ pred.quantile(measure=self.quantile_measure, q=self.quantile)
318
+ for pred in cv_predictions
319
+ ]
320
+ ).T
319
321
  else:
320
322
  X_pred = np.array(cv_predictions).T
321
323
  if y is not None:
@@ -31,8 +31,8 @@ class InverseVolatility(BaseOptimization):
31
31
  portfolio_params : dict, optional
32
32
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
33
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.
34
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
35
+ optimization model and passed to the portfolio.
36
36
 
37
37
  Attributes
38
38
  ----------
@@ -95,8 +95,8 @@ class EqualWeighted(BaseOptimization):
95
95
  portfolio_params : dict, optional
96
96
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
97
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.
98
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
99
+ optimization model and passed to the portfolio.
100
100
 
101
101
  Attributes
102
102
  ----------
@@ -139,8 +139,8 @@ class Random(BaseOptimization):
139
139
  portfolio_params : dict, optional
140
140
  Portfolio parameters passed to the portfolio evaluated by the `predict` and
141
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.
142
+ `management_fees`, `previous_weights` and `risk_free_rate` are copied from the
143
+ optimization model and passed to the portfolio.
144
144
 
145
145
  Attributes
146
146
  ----------
@@ -1,4 +1,4 @@
1
- """ Population module.
1
+ """Population module.
2
2
  A population is a collection of portfolios.
3
3
  """
4
4
 
@@ -7,6 +7,7 @@ A population is a collection of portfolios.
7
7
  # License: BSD 3 clause
8
8
 
9
9
  import inspect
10
+ from typing import Any
10
11
 
11
12
  import numpy as np
12
13
  import pandas as pd
@@ -15,7 +16,7 @@ import plotly.graph_objects as go
15
16
  import scipy.interpolate as sci
16
17
 
17
18
  import skfolio.typing as skt
18
- from skfolio.portfolio import BasePortfolio, MultiPeriodPortfolio, Portfolio
19
+ from skfolio.portfolio import BasePortfolio, MultiPeriodPortfolio
19
20
  from skfolio.utils.sorting import non_denominated_sort
20
21
  from skfolio.utils.tools import deduplicate_names
21
22
 
@@ -30,14 +31,14 @@ class Population(list):
30
31
 
31
32
  Parameters
32
33
  ----------
33
- iterable : list[Portfolio | MultiPeriodPortfolio]
34
+ iterable : list[BasePortfolio]
34
35
  The list of portfolios. Each item can be of type
35
36
  :class:`~skfolio.portfolio.Portfolio` and/or
36
37
  :class:`~skfolio.portfolio.MultiPeriodPortfolio`.
37
38
  Empty list are accepted.
38
39
  """
39
40
 
40
- def __init__(self, iterable: list[Portfolio | MultiPeriodPortfolio]) -> None:
41
+ def __init__(self, iterable: list[BasePortfolio]) -> None:
41
42
  super().__init__(self._validate_item(item) for item in iterable)
42
43
 
43
44
  def __repr__(self) -> str:
@@ -45,43 +46,43 @@ class Population(list):
45
46
 
46
47
  def __getitem__(
47
48
  self, indices: int | list[int] | slice
48
- ) -> "Portfolio | MultiPeriodPortfolio|Population":
49
+ ) -> "BasePortfolio | Population":
49
50
  item = super().__getitem__(indices)
50
51
  if isinstance(item, list):
51
52
  return self.__class__(item)
52
53
  return item
53
54
 
54
- def __setitem__(self, index: int, item: Portfolio | MultiPeriodPortfolio) -> None:
55
+ def __setitem__(self, index: int, item: BasePortfolio) -> None:
55
56
  super().__setitem__(index, self._validate_item(item))
56
57
 
57
- def __add__(self, other: Portfolio | MultiPeriodPortfolio) -> "Population":
58
+ def __add__(self, other: BasePortfolio) -> "Population":
58
59
  if not isinstance(other, Population):
59
60
  raise TypeError(
60
61
  f"Cannot add a Population with an object of type {type(other)}"
61
62
  )
62
63
  return self.__class__(super().__add__(other))
63
64
 
64
- def insert(self, index, item: Portfolio | MultiPeriodPortfolio) -> None:
65
+ def insert(self, index, item: BasePortfolio) -> None:
65
66
  """Insert portfolio before index."""
66
67
  super().insert(index, self._validate_item(item))
67
68
 
68
- def append(self, item: Portfolio | MultiPeriodPortfolio) -> None:
69
+ def append(self, item: BasePortfolio) -> None:
69
70
  """Append portfolio to the end of the population list."""
70
71
  super().append(self._validate_item(item))
71
72
 
72
- def extend(self, other: Portfolio | MultiPeriodPortfolio) -> None:
73
+ def extend(self, other: BasePortfolio) -> None:
73
74
  """Extend population list by appending elements from the iterable."""
74
75
  if isinstance(other, type(self)):
75
76
  super().extend(other)
76
77
  else:
77
78
  super().extend(self._validate_item(item) for item in other)
78
79
 
79
- def set_portfolio_params(self, **params: any) -> "Population":
80
+ def set_portfolio_params(self, **params: Any) -> "Population":
80
81
  """Set the parameters of all the portfolios.
81
82
 
82
83
  Parameters
83
84
  ----------
84
- **params : any
85
+ **params : Any
85
86
  Portfolio parameters.
86
87
 
87
88
  Returns
@@ -111,13 +112,14 @@ class Population(list):
111
112
 
112
113
  @staticmethod
113
114
  def _validate_item(
114
- item: Portfolio | MultiPeriodPortfolio,
115
- ) -> Portfolio | MultiPeriodPortfolio:
115
+ item: BasePortfolio,
116
+ ) -> BasePortfolio:
116
117
  """Validate that items are of type Portfolio or MultiPeriodPortfolio."""
117
- if isinstance(item, Portfolio | MultiPeriodPortfolio):
118
+ if isinstance(item, BasePortfolio):
118
119
  return item
119
120
  raise TypeError(
120
- "Population only accept items of type Portfolio and MultiPeriodPortfolio"
121
+ "Population only accept items that inherit from BasePortfolio such as "
122
+ "Portfolio or MultiPeriodPortfolio"
121
123
  f", got {type(item).__name__}"
122
124
  )
123
125
 
@@ -140,9 +142,12 @@ class Population(list):
140
142
  non-dominated portfolios.
141
143
  """
142
144
  n = len(self)
143
- if n > 0 and np.any([
144
- portfolio.fitness_measures != self[0].fitness_measures for portfolio in self
145
- ]):
145
+ if n > 0 and np.any(
146
+ [
147
+ portfolio.fitness_measures != self[0].fitness_measures
148
+ for portfolio in self
149
+ ]
150
+ ):
146
151
  raise ValueError(
147
152
  "Cannot compute non denominated sorting with Portfolios "
148
153
  "containing mixed `fitness_measures`"
@@ -187,11 +192,13 @@ class Population(list):
187
192
  return self.__class__(
188
193
  [portfolio for portfolio in self if portfolio.tag in tags]
189
194
  )
190
- return self.__class__([
191
- portfolio
192
- for portfolio in self
193
- if portfolio.name in names and portfolio.tag in tags
194
- ])
195
+ return self.__class__(
196
+ [
197
+ portfolio
198
+ for portfolio in self
199
+ if portfolio.name in names and portfolio.tag in tags
200
+ ]
201
+ )
195
202
 
196
203
  def measures(
197
204
  self,
@@ -319,7 +326,7 @@ class Population(list):
319
326
  q: float,
320
327
  names: skt.Names | None = None,
321
328
  tags: skt.Tags | None = None,
322
- ) -> Portfolio | MultiPeriodPortfolio:
329
+ ) -> BasePortfolio:
323
330
  """Returns the portfolio corresponding to the `q` quantile for a given portfolio
324
331
  measure.
325
332
 
@@ -339,7 +346,7 @@ class Population(list):
339
346
 
340
347
  Returns
341
348
  -------
342
- values : Portfolio | MultiPeriodPortfolio
349
+ values : BasePortfolio
343
350
  Portfolio corresponding to the `q` quantile for the measure.
344
351
  """
345
352
  if not 0 <= q <= 1:
@@ -355,7 +362,7 @@ class Population(list):
355
362
  measure: skt.Measure,
356
363
  names: skt.Names | None = None,
357
364
  tags: skt.Tags | None = None,
358
- ) -> Portfolio | MultiPeriodPortfolio:
365
+ ) -> BasePortfolio:
359
366
  """Returns the portfolio with the minimum measure.
360
367
 
361
368
  Parameters
@@ -371,7 +378,7 @@ class Population(list):
371
378
 
372
379
  Returns
373
380
  -------
374
- values : Portfolio | MultiPeriodPortfolio
381
+ values : BasePortfolio
375
382
  The portfolio with minimum measure.
376
383
  """
377
384
  return self.quantile(measure=measure, q=0, names=names, tags=tags)
@@ -381,7 +388,7 @@ class Population(list):
381
388
  measure: skt.Measure,
382
389
  names: skt.Names | None = None,
383
390
  tags: skt.Tags | None = None,
384
- ) -> Portfolio | MultiPeriodPortfolio:
391
+ ) -> BasePortfolio:
385
392
  """Returns the portfolio with the maximum measure.
386
393
 
387
394
  Parameters
@@ -397,7 +404,7 @@ class Population(list):
397
404
 
398
405
  Returns
399
406
  -------
400
- values : Portfolio | MultiPeriodPortfolio
407
+ values : BasePortfolio
401
408
  The portfolio with maximum measure.
402
409
  """
403
410
  return self.quantile(measure=measure, q=1, names=names, tags=tags)
@@ -784,15 +791,17 @@ class Population(list):
784
791
  x=xi,
785
792
  y=yi,
786
793
  z=Z,
787
- hovertemplate="<br>".join([
788
- str(e)
789
- + ": %{"
790
- + v
791
- + ":"
792
- + (",.3%" if not e.is_ratio else None)
793
- + "}"
794
- for e, v in [(x, "x"), (y, "y"), (z, "z")]
795
- ])
794
+ hovertemplate="<br>".join(
795
+ [
796
+ str(e)
797
+ + ": %{"
798
+ + v
799
+ + ":"
800
+ + (",.3%" if not e.is_ratio else None)
801
+ + "}"
802
+ for e, v in [(x, "x"), (y, "y"), (z, "z")]
803
+ ]
804
+ )
796
805
  + "<extra></extra>",
797
806
  colorbar=dict(
798
807
  title=str(z),
@@ -37,7 +37,6 @@
37
37
  # the class attributes with the argument name preceded by the measure name and
38
38
  # separated by '_'.
39
39
 
40
-
41
40
  import warnings
42
41
  from abc import abstractmethod
43
42
  from typing import ClassVar
@@ -8,7 +8,6 @@ is the dot product of the assets weights with the assets returns.
8
8
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
9
9
  # License: BSD 3 clause
10
10
 
11
-
12
11
  import numbers
13
12
  from typing import ClassVar
14
13
 
@@ -402,17 +401,19 @@ class Portfolio(BasePortfolio):
402
401
  """
403
402
 
404
403
  _read_only_attrs: ClassVar[set] = BasePortfolio._read_only_attrs.copy()
405
- _read_only_attrs.update({
406
- "X",
407
- "assets",
408
- "weights",
409
- "previous_weights",
410
- "transaction_costs",
411
- "management_fees",
412
- "n_assets",
413
- "total_cost",
414
- "total_fee",
415
- })
404
+ _read_only_attrs.update(
405
+ {
406
+ "X",
407
+ "assets",
408
+ "weights",
409
+ "previous_weights",
410
+ "transaction_costs",
411
+ "management_fees",
412
+ "n_assets",
413
+ "total_cost",
414
+ "total_fee",
415
+ }
416
+ )
416
417
 
417
418
  __slots__ = {
418
419
  # read-only
@@ -9,6 +9,7 @@
9
9
  # Grisel Licensed under BSD 3 clause.
10
10
 
11
11
  from abc import ABC, abstractmethod
12
+ from typing import Any
12
13
 
13
14
  import numpy as np
14
15
  import numpy.typing as npt
@@ -114,10 +115,12 @@ class LoadingMatrixRegression(BaseLoadingMatrix):
114
115
  self.loading_matrix_ = np.array(
115
116
  [self.multi_output_regressor_.estimators_[i].coef_ for i in range(n_assets)]
116
117
  )
117
- self.intercepts_ = np.array([
118
- self.multi_output_regressor_.estimators_[i].intercept_
119
- for i in range(n_assets)
120
- ])
118
+ self.intercepts_ = np.array(
119
+ [
120
+ self.multi_output_regressor_.estimators_[i].intercept_
121
+ for i in range(n_assets)
122
+ ]
123
+ )
121
124
 
122
125
 
123
126
  class FactorModel(BasePrior):
@@ -194,7 +197,7 @@ class FactorModel(BasePrior):
194
197
  self.max_iteration = max_iteration
195
198
 
196
199
  # noinspection PyMethodOverriding, PyPep8Naming
197
- def fit(self, X: npt.ArrayLike, y: any):
200
+ def fit(self, X: npt.ArrayLike, y: Any):
198
201
  """Fit the Factor Model estimator.
199
202
 
200
203
  Parameters
@@ -290,13 +290,15 @@ class _Dendrogram:
290
290
  Sets and returns default layout object for dendrogram figure.
291
291
 
292
292
  """
293
- self.layout.update({
294
- "showlegend": False,
295
- "autosize": False,
296
- "hovermode": "closest",
297
- "width": width,
298
- "height": height,
299
- })
293
+ self.layout.update(
294
+ {
295
+ "showlegend": False,
296
+ "autosize": False,
297
+ "hovermode": "closest",
298
+ "width": width,
299
+ "height": height,
300
+ }
301
+ )
300
302
 
301
303
  self.set_axis_layout(self.xaxis)
302
304
  self.set_axis_layout(self.yaxis)
skfolio/utils/tools.py CHANGED
@@ -10,6 +10,7 @@
10
10
  from collections.abc import Callable, Iterator
11
11
  from enum import Enum
12
12
  from functools import wraps
13
+ from typing import Any
13
14
 
14
15
  import numpy as np
15
16
  import numpy.typing as npt
@@ -41,7 +42,7 @@ class AutoEnum(str, Enum):
41
42
 
42
43
  @staticmethod
43
44
  def _generate_next_value_(
44
- name: str, start: int, count: int, last_values: any
45
+ name: str, start: int, count: int, last_values: Any
45
46
  ) -> str:
46
47
  """Overriding `auto()`"""
47
48
  return name.lower()
@@ -179,7 +180,7 @@ def args_names(func: object) -> list[str]:
179
180
 
180
181
 
181
182
  def check_estimator(
182
- estimator: skb.BaseEstimator | None, default: skb.BaseEstimator, check_type: any
183
+ estimator: skb.BaseEstimator | None, default: skb.BaseEstimator, check_type: Any
183
184
  ):
184
185
  """Check the estimator type and returns its cloned version it provided, otherwise
185
186
  return the default estimator.
@@ -192,7 +193,7 @@ def check_estimator(
192
193
  default : BaseEstimator
193
194
  Default estimator to return when `estimator` is `None`.
194
195
 
195
- check_type : any
196
+ check_type : Any
196
197
  Expected type of the estimator to check against.
197
198
 
198
199
  Returns
@@ -211,7 +212,7 @@ def check_estimator(
211
212
  def input_to_array(
212
213
  items: dict | npt.ArrayLike,
213
214
  n_assets: int,
214
- fill_value: any,
215
+ fill_value: Any,
215
216
  dim: int,
216
217
  assets_names: np.ndarray | None,
217
218
  name: str,
@@ -228,7 +229,7 @@ def input_to_array(
228
229
  Expected number of assets.
229
230
  Used to verify the shape of the converted array.
230
231
 
231
- fill_value : any
232
+ fill_value : Any
232
233
  When `items` is a dictionary, elements that are not in `asset_names` are filled
233
234
  with `fill_value` in the converted array.
234
235
 
@@ -424,7 +425,7 @@ def safe_split(
424
425
 
425
426
 
426
427
  def fit_single_estimator(
427
- estimator: any,
428
+ estimator: Any,
428
429
  X: npt.ArrayLike,
429
430
  y: npt.ArrayLike | None = None,
430
431
  indices: np.ndarray | None = None,
@@ -463,7 +464,7 @@ def fit_single_estimator(
463
464
 
464
465
 
465
466
  def fit_and_predict(
466
- estimator: any,
467
+ estimator: Any,
467
468
  X: npt.ArrayLike,
468
469
  y: npt.ArrayLike | None,
469
470
  train: np.ndarray,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: skfolio
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Portfolio optimization built on top of scikit-learn
5
5
  Author-email: Hugo Delatte <delatte.hugo@gmail.com>
6
6
  Maintainer-email: Hugo Delatte <delatte.hugo@gmail.com>
@@ -80,42 +80,41 @@ Requires-Dist: sphinx-sitemap ; extra == 'docs'
80
80
  Provides-Extra: tests
81
81
  Requires-Dist: pytest ; extra == 'tests'
82
82
  Requires-Dist: pytest-cov ; extra == 'tests'
83
- Requires-Dist: black ; extra == 'tests'
84
83
  Requires-Dist: ruff ; extra == 'tests'
85
84
 
86
85
  .. -*- mode: rst -*-
87
86
 
88
- |Licence|_ |Codecov|_ |Black|_ |PythonVersion|_ |PyPi|_ |CI/CD|_ |Downloads|_ |Ruff|_ |Contribution|_ |Website|_
87
+ |Licence| |Codecov| |Black| |PythonVersion| |PyPi| |CI/CD| |Downloads| |Ruff| |Contribution| |Website|
89
88
 
90
89
  .. |Licence| image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg
91
- .. _Licence: https://github.com/skfolio/skfolio/blob/main/LICENSE
90
+ :target: https://github.com/skfolio/skfolio/blob/main/LICENSE
92
91
 
93
92
  .. |Codecov| image:: https://codecov.io/gh/skfolio/skfolio/graph/badge.svg?token=KJ0SE4LHPV
94
- .. _Codecov: https://codecov.io/gh/skfolio/skfolio
93
+ :target: https://codecov.io/gh/skfolio/skfolio
95
94
 
96
- .. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue
97
- .. _PythonVersion: https://pypi.org/project/skfolio/
95
+ .. |PythonVersion| image:: https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue.svg
96
+ :target: https://pypi.org/project/skfolio/
98
97
 
99
98
  .. |PyPi| image:: https://img.shields.io/pypi/v/skfolio
100
- .. _PyPi: https://pypi.org/project/skfolio
99
+ :target: https://pypi.org/project/skfolio
101
100
 
102
101
  .. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
103
- .. _Black: https://github.com/psf/black
102
+ :target: https://github.com/psf/black
104
103
 
105
- .. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml?logo=github
106
- .. _CI/CD: https://github.com/skfolio/skfolio/raw/main/LICENSE
104
+ .. |CI/CD| image:: https://img.shields.io/github/actions/workflow/status/skfolio/skfolio/release.yml.svg?logo=github
105
+ :target: https://github.com/skfolio/skfolio/raw/main/LICENSE
107
106
 
108
107
  .. |Downloads| image:: https://static.pepy.tech/badge/skfolio
109
- .. _Downloads: https://pepy.tech/project/skfolio
108
+ :target: https://pepy.tech/project/skfolio
110
109
 
111
110
  .. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
112
- .. _Ruff: https://github.com/astral-sh/ruff
111
+ :target: https://github.com/astral-sh/ruff
113
112
 
114
113
  .. |Contribution| image:: https://img.shields.io/badge/Contributions-Welcome-blue
115
- .. _Contribution: https://github.com/skfolio/skfolio/blob/main/CONTRIBUTING.md
114
+ :target: https://github.com/skfolio/skfolio/blob/main/CONTRIBUTING.md
116
115
 
117
- .. |Website| image:: https://img.shields.io/website-up-down-53cc0d-red/http/skfolio.org
118
- .. _Website: https://skfolio.org
116
+ .. |Website| image:: https://img.shields.io/website.svg?down_color=red&down_message=down&up_color=53cc0d&up_message=up&url=https://skfolio.org
117
+ :target: https://skfolio.org
119
118
 
120
119
  .. |PythonMinVersion| replace:: 3.10
121
120
  .. |NumpyMinVersion| replace:: 1.23.4
@@ -645,9 +644,9 @@ If you use `skfolio` in a scientific publication, we would appreciate citations:
645
644
  Bibtex entry::
646
645
 
647
646
  @misc{skfolio,
648
- author = {Hugo Delatte, Carlo Nicolini},
649
- title = {skfolio},
650
- year = {2023},
651
- url = {https://github.com/skfolio/skfolio}
652
-
647
+ author = {Delatte, Hugo and Nicolini, Carlo},
648
+ title = {skfolio},
649
+ year = {2023},
650
+ url = {https://github.com/skfolio/skfolio}
651
+ }
653
652
 
@@ -4,22 +4,22 @@ skfolio/typing.py,sha256=yEZiCZ6UIyfYUqtfj9Kf2KA9mrjUbmxyzpH9uqVboJs,1378
4
4
  skfolio/cluster/__init__.py,sha256=4g-PFB_ld9BhiQ1ZPvvAorpFbRwd_p_DkeRlulDv2Hk,251
5
5
  skfolio/cluster/_hierarchical.py,sha256=SoISGgdyq4Rgqq1_TCzMbLv69c8a2Q91j_s9-jqaB3E,12817
6
6
  skfolio/datasets/__init__.py,sha256=9Tpf0Uj8wgr-g7xqvqQP4S4TUYDUNmNmK8t6lqBw2Fs,407
7
- skfolio/datasets/_base.py,sha256=GYvblFzTyfTs-Tij20FKWgVDCIT1n7y4uwx4TkZzdHc,13915
7
+ skfolio/datasets/_base.py,sha256=laAj8vE8evEKv6NAAJdypLrsfmlhqOJ27aP_AcpcxVQ,13952
8
8
  skfolio/datasets/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  skfolio/datasets/data/factors_dataset.csv.gz,sha256=brCJlT25DJo40yg1gnUXAakNtvWZZYR_1ksFeN5JcWE,36146
10
10
  skfolio/datasets/data/sp500_dataset.csv.gz,sha256=7iHKwovvsdCnOanOsiGE-ZU5RyaqDP3pohlB0awErA0,426065
11
11
  skfolio/datasets/data/sp500_index.csv.gz,sha256=iUw0QxwoT4aqZKRn4Xbio8m2l8hX65qzUAbC3VXT_fI,41898
12
12
  skfolio/distance/__init__.py,sha256=vpdmjFlJeI0AvPV3r5tp2zooAG4N9ihCwPlaqqdVj1w,547
13
13
  skfolio/distance/_base.py,sha256=jBgRk6lZrP1woSI9541fTfxBBkp4WCTLlRPmWcmA3j4,1326
14
- skfolio/distance/_distance.py,sha256=PHDqEourtqKSSFYiXKaUPQQqNFEqIEBhWcbWwe3fxjg,18539
14
+ skfolio/distance/_distance.py,sha256=nQRm141leyCXtjxqXNhsTPtq2dATbpyyEQHKhZ98rE4,18538
15
15
  skfolio/measures/__init__.py,sha256=9ThQikIAQcfKRLSCoMr-Z5vE2-ThtYe9B-L40b6Ewg0,1631
16
- skfolio/measures/_enums.py,sha256=oy8wPm3EGsHokgLXe9xfUKet4vAuvch0cJuISH9RNvk,8902
17
- skfolio/measures/_measures.py,sha256=zIYRxSJNfLzzh3G2ZK8zyPJzwN5nbSBt5NW0dbrKosc,16830
16
+ skfolio/measures/_enums.py,sha256=NJcngwg9b2JMMiekwkWU9POfnDvgfUgtYtyV2VSFDVM,8934
17
+ skfolio/measures/_measures.py,sha256=VU9tahJKJvt8PaJZNxWkmFkFU4PsN0FfYO32Je6D53E,16829
18
18
  skfolio/metrics/__init__.py,sha256=MomHJ5_bgjq4qUwGS2bfhNmG_ld0oQ4wK6y0Yy_Eonc,75
19
19
  skfolio/metrics/_scorer.py,sha256=h1VuZk-zzn4rIChHl9FvM7RxqVT3b-jR1CEB-cr9F2s,4306
20
- skfolio/model_selection/__init__.py,sha256=QYYm5lYyioZuPnsTu-b3lz-tCcl3Gwrx-y9IIvep13A,453
21
- skfolio/model_selection/_combinatorial.py,sha256=z4nhqIq98iGMP9I3GM8ng1WRJDJQn4eQ56bYaw5JWog,14716
22
- skfolio/model_selection/_validation.py,sha256=uPk9HhOak1jh3PEjNMyi3jx4PzIW2fxnqdC7u1yWGwY,7644
20
+ skfolio/model_selection/__init__.py,sha256=8j9Z5tpbgBScjFbn8ZsCm_6rZO7RkPQ1QIF8BqYMVA8,507
21
+ skfolio/model_selection/_combinatorial.py,sha256=DHfbnjqaOGV5bT_9pGuoR23JN6iEp8Tn0Kr6sLq1Xmg,19045
22
+ skfolio/model_selection/_validation.py,sha256=Rs3j6LFfm8HJCnaoUXlPnNsqxIyjRmMhmGWvvJ0olXA,7686
23
23
  skfolio/model_selection/_walk_forward.py,sha256=pbMD8VM1OoKiEtrlPeCLG29xVijsq1RArSjTvGO1FnU,7539
24
24
  skfolio/moments/__init__.py,sha256=BaP6FjU-CEsV_mcC2AOKXNFy-_wXZsk-jqzJG6mTpsM,746
25
25
  skfolio/moments/covariance/__init__.py,sha256=KuzhGuiu-eOwQMgWmEhFhqCcOmiayrYp5yeL1NsFW98,563
@@ -29,30 +29,30 @@ skfolio/moments/expected_returns/__init__.py,sha256=NETEcKKYKsQvzUU4ZIq6mgT14zdr
29
29
  skfolio/moments/expected_returns/_base.py,sha256=xk9mzi48uCOHaMTGQBMr3FU7Ai_shxYhmGeOsVwjv9Q,871
30
30
  skfolio/moments/expected_returns/_expected_returns.py,sha256=T_tt_dsYgXrjIM2REUii7_4_R8IEc3xdtuv45yYM-5o,13388
31
31
  skfolio/optimization/__init__.py,sha256=vXIbwWJL48zJ1jy7ZB2PPBVx7rZo0vVA8QQQuD-L6ts,1021
32
- skfolio/optimization/_base.py,sha256=5GEMjlw5jrbygTp-4ZCk7cmwjJ0PBOoNdULqypVlfQM,5606
32
+ skfolio/optimization/_base.py,sha256=LoRONJP70AwbFpdgqVS_g145pCx0JGkazjWvkQzT_iM,5748
33
33
  skfolio/optimization/cluster/__init__.py,sha256=M3xVdYhNKp4e9CB7hzb4yjTxkkNCHh7Mt_KGFFrkOgs,388
34
34
  skfolio/optimization/cluster/_nco.py,sha256=MKzNvDIAbqS_D41Vp7tNa7OrXYq_bYVSQEVbVCHOEWA,14834
35
35
  skfolio/optimization/cluster/hierarchical/__init__.py,sha256=YnfcPHvjwB6kcG4hoQqc0NqIJKaG7OjBtmXNbOxCq08,405
36
- skfolio/optimization/cluster/hierarchical/_base.py,sha256=0FtVcrMOFsQIJVaShf_qoHBRSuVmR4qzmJdvJc214HI,17255
37
- skfolio/optimization/cluster/hierarchical/_herc.py,sha256=JyXYX4PE17alCzIv6piF-G7_nprgzdzfYp35kjAeWMo,17235
38
- skfolio/optimization/cluster/hierarchical/_hrp.py,sha256=xdnbYNcY1Vpv-mDw-JAGz5o--Rl2b8CfGzyunLJe75M,16121
36
+ skfolio/optimization/cluster/hierarchical/_base.py,sha256=BVJ8Yq2Yg3Z0iqgx0HwS2OxPLVruoSzrelyYQmt_AiQ,17281
37
+ skfolio/optimization/cluster/hierarchical/_herc.py,sha256=p7VdEeJOD5ykA1H9FOLaEqIL6D_YYF8VXXfAqTSRdjs,17238
38
+ skfolio/optimization/cluster/hierarchical/_hrp.py,sha256=i3bliOjWxDX3V3vekhl_Gf_MZ4HroU4r_3rAR-QnyK0,16123
39
39
  skfolio/optimization/convex/__init__.py,sha256=F6BPFikTo0B-7JCKazqLGEwM3RkgTNbFm5GAGkaq9Uo,570
40
- skfolio/optimization/convex/_base.py,sha256=CyEP2JXP0ISnEZRsV-tumH9wwPGBbyM6MDf1B-jCxyw,75413
41
- skfolio/optimization/convex/_distributionally_robust.py,sha256=nQW_MD0wlVY_tvLvRq_CvezOn4q92YC7wHfzFE9k9tY,17308
42
- skfolio/optimization/convex/_maximum_diversification.py,sha256=8wQn5eFEat5wixmu0cfaswAee8D5C-hzV4s5ntUoAFM,19137
43
- skfolio/optimization/convex/_mean_risk.py,sha256=ivAjLXis4fUZ9XOvh_iOemfnvgDAzrK3LDvrlaa2TpA,43130
44
- skfolio/optimization/convex/_risk_budgeting.py,sha256=wXCgn64w2alAGG-dGJ8IwFrhYVtybM-O2hXiFaXietg,23618
40
+ skfolio/optimization/convex/_base.py,sha256=xvhgSQ5X7Lqz6kzGtldlDLIMV61gwrphW9_qMQSG498,75439
41
+ skfolio/optimization/convex/_distributionally_robust.py,sha256=PHAZvXb_zRXpeDD90fgxVEQAuYH5EY8fTpYQ8jO9shA,17311
42
+ skfolio/optimization/convex/_maximum_diversification.py,sha256=vqUJZdR28X75LQ18XXWLGd7yX9-f5wvOByN0UVSyd_Y,19140
43
+ skfolio/optimization/convex/_mean_risk.py,sha256=q9aH_thsmKeK_3-8jQOLN1S9z9DXvZmYGgj0a2ezAl0,43134
44
+ skfolio/optimization/convex/_risk_budgeting.py,sha256=pRMTA-CCTsV-yysADA3b9RDjLGPk4Jw0eP5ZVQ5W_sc,23622
45
45
  skfolio/optimization/ensemble/__init__.py,sha256=8TXxcxH2_gG3C1xtgQj9OHHr0Le8lhdejtlURL6T3ZY,158
46
- skfolio/optimization/ensemble/_base.py,sha256=vOi08U1MjoceD_xMFKTdM8egd1sY0GsCh1BSJuKH_fQ,3445
47
- skfolio/optimization/ensemble/_stacking.py,sha256=3yqGHE1MpnugkJ0Z6fp7bfYLOuImsfmeiMQ8XDdIdZY,13098
46
+ skfolio/optimization/ensemble/_base.py,sha256=GaNDQu6ivosYuwMrb-b0PhToCsNrmhSYyXkxeM8W4rU,3399
47
+ skfolio/optimization/ensemble/_stacking.py,sha256=2PodXf_JahToShSAbwQRXosEer3ESOAhpsnB6SCwztk,13148
48
48
  skfolio/optimization/naive/__init__.py,sha256=Dkr55R48urC-jfYN007NTbei16N91Na_EDYLVqzhGgQ,147
49
- skfolio/optimization/naive/_naive.py,sha256=sadsU8TnNRf28EDLg-KZ4yJm2FvV204vdsb1MhVJyMQ,5561
49
+ skfolio/optimization/naive/_naive.py,sha256=lXBUK0-JZd_30xUqnwv3IN1-yvKNVrub2A4ZVdfjmnY,5570
50
50
  skfolio/population/__init__.py,sha256=rsPPMUv95aTK7vmpPeQwF8NzFuBwk6RDo5g4HNaPzNM,80
51
- skfolio/population/_population.py,sha256=cyHdpZr6Ib725CRE_rr0irI4sHEcVEAY-Gmy--1goKo,29242
51
+ skfolio/population/_population.py,sha256=DNBjR2Ob_Rz3B70mwLIrWd_ElOvHxDWjN3EQs-rGHCk,29148
52
52
  skfolio/portfolio/__init__.py,sha256=YYtcAPmA2zeCxFGTXegg2FXcA7py6CxOX7IMTdYuXl0,586
53
- skfolio/portfolio/_base.py,sha256=XHSFrQJRY6nIyUG8sd8ZgbwZt7_ZKlMRNCfOqdls0Rw,38314
53
+ skfolio/portfolio/_base.py,sha256=jO6YKXu8oxE-uldl2CayLvbA40yfeCA1vNa_xm-WcWo,38313
54
54
  skfolio/portfolio/_multi_period_portfolio.py,sha256=63RwL0oTn-3L5LON6f2e9J58zw9q8S2gQ5c8JfSnN28,22759
55
- skfolio/portfolio/_portfolio.py,sha256=DG50qI5BWRwQfuo8mFdS398V4aEHXKXpQzQQkXf_ADI,31596
55
+ skfolio/portfolio/_portfolio.py,sha256=I2P-ogv78wx6aRytHN7z8BHLkye-E8T5LinVARgsuIQ,31649
56
56
  skfolio/pre_selection/__init__.py,sha256=VtUtDn-U-Mn_xR2k7yfld0Yb0rPhLakEAiBwUyi-4Z8,189
57
57
  skfolio/pre_selection/_pre_selection.py,sha256=w84T14nKmzkgzbw5CW_AIlci741lXYxKUwB5pBjhTTI,12163
58
58
  skfolio/preprocessing/__init__.py,sha256=15A1bzfPsbfxxXgGP1gstf4R0E_347Wn18z5W5jH-hk,94
@@ -61,7 +61,7 @@ skfolio/prior/__init__.py,sha256=jql8NTiWlykPKJUXTOPdqm531mP8Pul1QAR6hXTXA6c,446
61
61
  skfolio/prior/_base.py,sha256=Dx6rX0X6ymDiieFOI-ik3xMNNFhYEtwLSXOdajf5wZY,1927
62
62
  skfolio/prior/_black_litterman.py,sha256=JuwVXLXY5qHETBZDg0wmBGMSQehJp_1t7c1-kanKaiU,9397
63
63
  skfolio/prior/_empirical.py,sha256=YhCYW8R3vqs3ntF4WSokuwLW6Gd98df7f8LL8zCS-D0,5740
64
- skfolio/prior/_factor_model.py,sha256=E42sx_CbT-GbkT-BBb59XrUP4DMYJzuf3I2MEcGEnyE,9608
64
+ skfolio/prior/_factor_model.py,sha256=xC-womM7oC91jRaIQP7MSiRPkwm5b7pkWHi-5mOb2jY,9665
65
65
  skfolio/uncertainty_set/__init__.py,sha256=LlMHtYv9G9fgtM7m4sCSToS9et57Pm2Q2gGchTVrj6c,617
66
66
  skfolio/uncertainty_set/_base.py,sha256=rZ3g2AhDKFQTPajgh6Fz5S5TTf0qM4Ie6RGxPhp32D8,3301
67
67
  skfolio/uncertainty_set/_bootstrap.py,sha256=Rig8ZGc8rEOnYygPyTaDSu5lp3wGuZrQVRoJTm3UuAA,10301
@@ -71,11 +71,11 @@ skfolio/utils/bootstrap.py,sha256=3zY2kO_GQURKEcQMCasJOSByde9Mt2IAi3KJH0_a4mk,35
71
71
  skfolio/utils/equations.py,sha256=w0HsYjA7cS0mHYsI9MpixHLkof3HN26nc14ZfqFrHlE,11047
72
72
  skfolio/utils/sorting.py,sha256=lSjMvH2L-sSj-06B3MlwBrH1rtjCeGEe4hG894W7TE0,3504
73
73
  skfolio/utils/stats.py,sha256=IP36nMc5j5Hcqjbg7lvDIsGp1GWRdOh5jU3W6Z8nkYs,13132
74
- skfolio/utils/tools.py,sha256=roj4zTwmfunPb8HtxcOPAsCKpk0ZPEPnQztSglE9t4o,15351
74
+ skfolio/utils/tools.py,sha256=xa42f7U3Ki8-CJS6g8w7bKCLI_QMJ8D6LxLBjlEM7Ok,15374
75
75
  skfolio/utils/fixes/__init__.py,sha256=knHau8PRZP07XDHR59CW8VWxkpTP0gdr6RAHJrO-zaA,95
76
- skfolio/utils/fixes/_dendrogram.py,sha256=QK0OYAmlDe4sdrbFtRvxIlKfPlOGyA2zmsZIz1fIMsM,13505
77
- skfolio-0.1.2.dist-info/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
78
- skfolio-0.1.2.dist-info/METADATA,sha256=BLSxr_RLZ2Y5QFsJhlHjnGp7WUGPMiQxJq59jsY0t50,19613
79
- skfolio-0.1.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
80
- skfolio-0.1.2.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
81
- skfolio-0.1.2.dist-info/RECORD,,
76
+ skfolio/utils/fixes/_dendrogram.py,sha256=9aIhSnMwpQHJhQx7IpXC3jlw6YJ3H4XQnnx_d4nMllQ,13551
77
+ skfolio-0.2.0.dist-info/LICENSE,sha256=F6Gi-ZJX5BlVzYK8R9NcvAkAsKa7KO29xB1OScbrH6Q,1526
78
+ skfolio-0.2.0.dist-info/METADATA,sha256=mTx4X6Oymgb_VzpoAXdel_WnafJFUR6Q5rKuL9Td4l8,19607
79
+ skfolio-0.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
80
+ skfolio-0.2.0.dist-info/top_level.txt,sha256=NXEaoS9Ms7t32gxkb867nV0OKlU0KmssL7IJBVo0fJs,8
81
+ skfolio-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5