skfolio 0.0.1__py3-none-any.whl → 0.0.3__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 (33) hide show
  1. skfolio/cluster/_hierarchical.py +1 -0
  2. skfolio/datasets/_base.py +1 -1
  3. skfolio/measures/__init__.py +1 -1
  4. skfolio/model_selection/_combinatorial.py +12 -14
  5. skfolio/model_selection/_validation.py +6 -8
  6. skfolio/moments/covariance/_covariance.py +0 -1
  7. skfolio/moments/expected_returns/_expected_returns.py +1 -0
  8. skfolio/optimization/_base.py +9 -11
  9. skfolio/optimization/cluster/hierarchical/_base.py +0 -2
  10. skfolio/optimization/convex/__init__.py +1 -1
  11. skfolio/optimization/convex/_base.py +43 -26
  12. skfolio/optimization/convex/_distributionally_robust.py +22 -10
  13. skfolio/optimization/convex/_maximum_diversification.py +17 -7
  14. skfolio/optimization/convex/_mean_risk.py +25 -13
  15. skfolio/optimization/convex/_risk_budgeting.py +22 -10
  16. skfolio/optimization/ensemble/_stacking.py +4 -6
  17. skfolio/population/_population.py +18 -25
  18. skfolio/portfolio/_portfolio.py +11 -13
  19. skfolio/pre_selection/_pre_selection.py +6 -6
  20. skfolio/preprocessing/_returns.py +1 -1
  21. skfolio/prior/__init__.py +1 -1
  22. skfolio/prior/_base.py +1 -0
  23. skfolio/prior/_empirical.py +1 -1
  24. skfolio/prior/_factor_model.py +4 -6
  25. skfolio/uncertainty_set/_base.py +1 -0
  26. skfolio/uncertainty_set/_bootstrap.py +1 -0
  27. skfolio/uncertainty_set/_empirical.py +2 -0
  28. skfolio/utils/stats.py +1 -1
  29. {skfolio-0.0.1.dist-info → skfolio-0.0.3.dist-info}/METADATA +580 -568
  30. {skfolio-0.0.1.dist-info → skfolio-0.0.3.dist-info}/RECORD +33 -33
  31. {skfolio-0.0.1.dist-info → skfolio-0.0.3.dist-info}/LICENSE +0 -0
  32. {skfolio-0.0.1.dist-info → skfolio-0.0.3.dist-info}/WHEEL +0 -0
  33. {skfolio-0.0.1.dist-info → skfolio-0.0.3.dist-info}/top_level.txt +0 -0
@@ -302,14 +302,17 @@ class RiskBudgeting(ConvexOptimization):
302
302
  It is a function that must take as argument the weights `w` and returns a
303
303
  CVPXY expression.
304
304
 
305
- solver : str, optional
306
- The solver to use. For example, "ECOS", "SCS", or "OSQP".
307
- The default (`None`) is set depending on the problem.
305
+ solver : str, default="CLARABEL"
306
+ The solver to use. The default is "CLARABEL" which is written in Rust and has
307
+ better numerical stability and performance than ECOS and SCS. Cvxpy will replace
308
+ its default solver "ECOS" by "CLARABEL" in future releases.
308
309
  For more details about available solvers, check the CVXPY documentation:
309
310
  https://www.cvxpy.org/tutorial/advanced/index.html#choosing-a-solver
310
311
 
311
312
  solver_params : dict, optional
312
313
  Solver parameters. For example, `solver_params=dict(verbose=True)`.
314
+ The default (`None`) is use `{"tol_gap_abs": 1e-9, "tol_gap_rel": 1e-9}`
315
+ for the solver "CLARABEL" and the CVXPY default otherwise.
313
316
  For more details about solver arguments, check the CVXPY documentation:
314
317
  https://www.cvxpy.org/tutorial/advanced/index.html#setting-solver-options
315
318
 
@@ -323,6 +326,10 @@ class RiskBudgeting(ConvexOptimization):
323
326
  It can be used to increase the optimization accuracies in specific cases.
324
327
  The default (`None`) is set depending on the problem.
325
328
 
329
+ save_problem : bool, default=False
330
+ If this is set to True, the CVXPY Problem is saved in `problem_`.
331
+ The default is `False`.
332
+
326
333
  raise_on_failure : bool, default=True
327
334
  If this is set to True, an error is raised when the optimization fail otherwise
328
335
  it passes with a warning.
@@ -338,15 +345,16 @@ class RiskBudgeting(ConvexOptimization):
338
345
  weights_ : ndarray of shape (n_assets,) or (n_optimizations, n_assets)
339
346
  Weights of the assets.
340
347
 
341
- problem_: cvxpy.Problem
342
- CVXPY problem used for the optimization.
343
-
344
348
  problem_values_ : dict[str, float] | list[dict[str, float]] of size n_optimizations
345
349
  Expression values retrieved from the CVXPY problem.
346
350
 
347
351
  prior_estimator_ : BasePrior
348
352
  Fitted `prior_estimator`.
349
353
 
354
+ problem_: cvxpy.Problem
355
+ CVXPY problem used for the optimization. Only when `save_problem` is set to
356
+ `True`.
357
+
350
358
  n_features_in_ : int
351
359
  Number of assets seen during `fit`.
352
360
 
@@ -376,10 +384,11 @@ class RiskBudgeting(ConvexOptimization):
376
384
  evar_beta: float = 0.95,
377
385
  cdar_beta: float = 0.95,
378
386
  edar_beta: float = 0.95,
379
- solver: str | None = None,
387
+ solver: str = "CLARABEL",
380
388
  solver_params: dict | None = None,
381
389
  scale_objective: float | None = None,
382
390
  scale_constraints: float | None = None,
391
+ save_problem: bool = False,
383
392
  raise_on_failure: bool = True,
384
393
  add_objective: skt.ExpressionFunction | None = None,
385
394
  add_constraints: skt.ExpressionFunction | None = None,
@@ -409,6 +418,7 @@ class RiskBudgeting(ConvexOptimization):
409
418
  solver_params=solver_params,
410
419
  scale_objective=scale_objective,
411
420
  scale_constraints=scale_constraints,
421
+ save_problem=save_problem,
412
422
  raise_on_failure=raise_on_failure,
413
423
  add_objective=add_objective,
414
424
  add_constraints=add_constraints,
@@ -450,7 +460,6 @@ class RiskBudgeting(ConvexOptimization):
450
460
  self._validation()
451
461
  # Used to avoid adding multiple times similar constrains linked to identical
452
462
  # risk models
453
- self._clear_models_cache()
454
463
  self.prior_estimator_ = check_estimator(
455
464
  self.prior_estimator,
456
465
  default=EmpiricalPrior(),
@@ -460,8 +469,11 @@ class RiskBudgeting(ConvexOptimization):
460
469
  prior_model = self.prior_estimator_.prior_model_
461
470
  n_observations, n_assets = prior_model.returns.shape
462
471
 
463
- # set solvers
464
- self._set_solver(default="SCS")
472
+ # set solvers params
473
+ if self.solver == "CLARABEL":
474
+ self._set_solver_params(default={"tol_gap_abs": 1e-9, "tol_gap_rel": 1e-9})
475
+ else:
476
+ self._set_solver_params(default=None)
465
477
 
466
478
  # set scale
467
479
  self._set_scale_objective(default=1)
@@ -306,12 +306,10 @@ class StackingOptimization(BaseOptimization, BaseComposition):
306
306
  _ = self._validate_data(X)
307
307
 
308
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
309
+ X_pred = np.array([
310
+ pred.quantile(measure=self.quantile_measure, q=self.quantile)
311
+ for pred in cv_predictions
312
+ ]).T
315
313
  else:
316
314
  X_pred = np.array(cv_predictions).T
317
315
  if y is not None:
@@ -139,12 +139,9 @@ class Population(list):
139
139
  non-dominated portfolios.
140
140
  """
141
141
  n = len(self)
142
- if n > 0 and np.any(
143
- [
144
- portfolio.fitness_measures != self[0].fitness_measures
145
- for portfolio in self
146
- ]
147
- ):
142
+ if n > 0 and np.any([
143
+ portfolio.fitness_measures != self[0].fitness_measures for portfolio in self
144
+ ]):
148
145
  raise ValueError(
149
146
  "Cannot compute non denominated sorting with Portfolios "
150
147
  "containing mixed `fitness_measures`"
@@ -189,13 +186,11 @@ class Population(list):
189
186
  return self.__class__(
190
187
  [portfolio for portfolio in self if portfolio.tag in tags]
191
188
  )
192
- return self.__class__(
193
- [
194
- portfolio
195
- for portfolio in self
196
- if portfolio.name in names and portfolio.tag in tags
197
- ]
198
- )
189
+ return self.__class__([
190
+ portfolio
191
+ for portfolio in self
192
+ if portfolio.name in names and portfolio.tag in tags
193
+ ])
199
194
 
200
195
  def measures(
201
196
  self,
@@ -759,7 +754,7 @@ class Population(list):
759
754
  fronts = self.non_denominated_sort(first_front_only=False)
760
755
  if tags is not None:
761
756
  ValueError("Cannot plot front with tags selected")
762
- df["front"] = -1
757
+ df["front"] = str(-1)
763
758
  for i, front in enumerate(fronts):
764
759
  for idx in front:
765
760
  df.iloc[idx, -1] = str(i)
@@ -788,17 +783,15 @@ class Population(list):
788
783
  x=xi,
789
784
  y=yi,
790
785
  z=Z,
791
- hovertemplate="<br>".join(
792
- [
793
- str(e)
794
- + ": %{"
795
- + v
796
- + ":"
797
- + (",.3%" if not e.is_ratio else None)
798
- + "}"
799
- for e, v in [(x, "x"), (y, "y"), (z, "z")]
800
- ]
801
- )
786
+ hovertemplate="<br>".join([
787
+ str(e)
788
+ + ": %{"
789
+ + v
790
+ + ":"
791
+ + (",.3%" if not e.is_ratio else None)
792
+ + "}"
793
+ for e, v in [(x, "x"), (y, "y"), (z, "z")]
794
+ ])
802
795
  + "<extra></extra>",
803
796
  colorbar=dict(
804
797
  title=str(z),
@@ -401,19 +401,17 @@ class Portfolio(BasePortfolio):
401
401
  """
402
402
 
403
403
  _read_only_attrs: ClassVar[set] = BasePortfolio._read_only_attrs.copy()
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
- )
404
+ _read_only_attrs.update({
405
+ "X",
406
+ "assets",
407
+ "weights",
408
+ "previous_weights",
409
+ "transaction_costs",
410
+ "management_fees",
411
+ "n_assets",
412
+ "total_cost",
413
+ "total_fee",
414
+ })
417
415
 
418
416
  __slots__ = {
419
417
  # read-only
@@ -296,13 +296,13 @@ class SelectNonDominated(skf.SelectorMixin, skb.BaseEstimator):
296
296
  )
297
297
 
298
298
  # Add pairs with correlation below threshold with minimum variance
299
- # ptf_variance = 𝜎1^2 𝑤1^2 + 𝜎2^2 𝑤2^2 + 2 𝜎12 𝑤1 𝑤2 (1)
300
- # with 𝑤1 + 𝑤2 = 1
301
- # To find the minimum we substitute 𝑤2 = 1 - 𝑤1 in (1) and differentiate with
302
- # respect to 𝑤1 and set to zero.
299
+ # ptf_variance = sigma1^2 w1^2 + sigma2^2 w2^2 + 2 sigma12 w1 w2 (1)
300
+ # with w1 + w2 = 1
301
+ # To find the minimum we substitute w2 = 1 - w1 in (1) and differentiate with
302
+ # respect to w1 and set to zero.
303
303
  # By solving the obtained equation, we get:
304
- # 𝑤1 = (𝜎2^2 - 𝜎12) / (𝜎1^2 + 𝜎2^2 - 2 𝜎12)
305
- # 𝑤2 = 1 - 𝑤1
304
+ # w1 = (sigma2^2 - sigma12) / (sigma1^2 + sigma2^2 - 2 sigma12)
305
+ # w2 = 1 - w1
306
306
 
307
307
  corr = np.corrcoef(X.T)
308
308
  covariance = np.cov(X.T)
@@ -69,7 +69,7 @@ def prices_to_returns(
69
69
 
70
70
  References
71
71
  ----------
72
- .. [1] "Linear vs. Compounded Returns Common Pitfalls in Portfolio Management".
72
+ .. [1] "Linear vs. Compounded Returns - Common Pitfalls in Portfolio Management".
73
73
  GARP Risk Professional.
74
74
  Attilio Meucci (2010).
75
75
  """
skfolio/prior/__init__.py CHANGED
@@ -2,8 +2,8 @@ from skfolio.prior._base import BasePrior, PriorModel
2
2
  from skfolio.prior._black_litterman import BlackLitterman
3
3
  from skfolio.prior._empirical import EmpiricalPrior
4
4
  from skfolio.prior._factor_model import (
5
- FactorModel,
6
5
  BaseLoadingMatrix,
6
+ FactorModel,
7
7
  LoadingMatrixRegression,
8
8
  )
9
9
 
skfolio/prior/_base.py CHANGED
@@ -36,6 +36,7 @@ class PriorModel:
36
36
  some optimizations (for example in mean-variance) to improve performance and
37
37
  convergence. The default is `None`.
38
38
  """
39
+
39
40
  mu: np.ndarray
40
41
  covariance: np.ndarray
41
42
  returns: np.ndarray
@@ -64,7 +64,7 @@ class EmpiricalPrior(BasePrior):
64
64
 
65
65
  References
66
66
  ----------
67
- .. [1] "Linear vs. Compounded Returns Common Pitfalls in Portfolio Management".
67
+ .. [1] "Linear vs. Compounded Returns - Common Pitfalls in Portfolio Management".
68
68
  GARP Risk Professional.
69
69
  Attilio Meucci (2010).
70
70
  """
@@ -109,12 +109,10 @@ class LoadingMatrixRegression(BaseLoadingMatrix):
109
109
  self.loading_matrix_ = np.array(
110
110
  [self.multi_output_regressor_.estimators_[i].coef_ for i in range(n_assets)]
111
111
  )
112
- self.intercepts_ = np.array(
113
- [
114
- self.multi_output_regressor_.estimators_[i].intercept_
115
- for i in range(n_assets)
116
- ]
117
- )
112
+ self.intercepts_ = np.array([
113
+ self.multi_output_regressor_.estimators_[i].intercept_
114
+ for i in range(n_assets)
115
+ ])
118
116
 
119
117
 
120
118
  class FactorModel(BasePrior):
@@ -37,6 +37,7 @@ class UncertaintySet:
37
37
  sigma : ndarray of shape (n_assets)
38
38
  Shape of the ellipsoid :math:`S`
39
39
  """
40
+
40
41
  k: float
41
42
  sigma: np.ndarray
42
43
 
@@ -80,6 +80,7 @@ class BootstrapMuUncertaintySet(BaseMuUncertaintySet):
80
80
  Bootstrap",
81
81
  Patton, Politis & White (2009).
82
82
  """
83
+
83
84
  prior_estimator_: BasePrior
84
85
 
85
86
  def __init__(
@@ -71,6 +71,7 @@ class EmpiricalMuUncertaintySet(BaseMuUncertaintySet):
71
71
  Optimization: A Journal of Mathematical Programming and Operations Research,
72
72
  Schöttle & Werner (2009).
73
73
  """
74
+
74
75
  prior_estimator_: BasePrior
75
76
 
76
77
  def __init__(
@@ -178,6 +179,7 @@ class EmpiricalCovarianceUncertaintySet(BaseCovarianceUncertaintySet):
178
179
  Optimization: A Journal of Mathematical Programming and Operations Research,
179
180
  Schöttle & Werner (2009).
180
181
  """
182
+
181
183
  prior_estimator_: BasePrior
182
184
 
183
185
  def __init__(
skfolio/utils/stats.py CHANGED
@@ -100,7 +100,7 @@ def n_bins_knuth(x: np.ndarray) -> int:
100
100
  n = len(x)
101
101
 
102
102
  def func(y: float):
103
- y = int(y)
103
+ y = y[0]
104
104
  if y <= 0:
105
105
  return np.inf
106
106
  bin_edges = np.linspace(x[0], x[-1], int(y) + 1)