skfolio 0.7.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 (114) hide show
  1. skfolio/__init__.py +2 -2
  2. skfolio/cluster/__init__.py +1 -1
  3. skfolio/cluster/_hierarchical.py +1 -1
  4. skfolio/datasets/__init__.py +1 -1
  5. skfolio/datasets/_base.py +2 -2
  6. skfolio/datasets/data/__init__.py +1 -0
  7. skfolio/distance/__init__.py +1 -1
  8. skfolio/distance/_base.py +2 -2
  9. skfolio/distance/_distance.py +4 -4
  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 +6 -6
  34. skfolio/measures/__init__.py +1 -1
  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 +2 -2
  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/covariance/_base.py +1 -1
  44. skfolio/moments/covariance/_denoise_covariance.py +1 -1
  45. skfolio/moments/covariance/_detone_covariance.py +1 -1
  46. skfolio/moments/covariance/_empirical_covariance.py +1 -1
  47. skfolio/moments/covariance/_ew_covariance.py +1 -1
  48. skfolio/moments/covariance/_gerber_covariance.py +1 -1
  49. skfolio/moments/covariance/_graphical_lasso_cv.py +1 -1
  50. skfolio/moments/covariance/_implied_covariance.py +2 -7
  51. skfolio/moments/covariance/_ledoit_wolf.py +1 -1
  52. skfolio/moments/covariance/_oas.py +1 -1
  53. skfolio/moments/covariance/_shrunk_covariance.py +1 -1
  54. skfolio/moments/expected_returns/_base.py +1 -1
  55. skfolio/moments/expected_returns/_empirical_mu.py +1 -1
  56. skfolio/moments/expected_returns/_equilibrium_mu.py +1 -1
  57. skfolio/moments/expected_returns/_ew_mu.py +1 -1
  58. skfolio/moments/expected_returns/_shrunk_mu.py +2 -2
  59. skfolio/optimization/__init__.py +2 -0
  60. skfolio/optimization/_base.py +2 -2
  61. skfolio/optimization/cluster/__init__.py +2 -0
  62. skfolio/optimization/cluster/_nco.py +7 -7
  63. skfolio/optimization/cluster/hierarchical/__init__.py +2 -0
  64. skfolio/optimization/cluster/hierarchical/_base.py +1 -2
  65. skfolio/optimization/cluster/hierarchical/_herc.py +2 -2
  66. skfolio/optimization/cluster/hierarchical/_hrp.py +2 -2
  67. skfolio/optimization/convex/__init__.py +2 -0
  68. skfolio/optimization/convex/_base.py +8 -8
  69. skfolio/optimization/convex/_distributionally_robust.py +4 -4
  70. skfolio/optimization/convex/_maximum_diversification.py +5 -5
  71. skfolio/optimization/convex/_mean_risk.py +5 -6
  72. skfolio/optimization/convex/_risk_budgeting.py +3 -3
  73. skfolio/optimization/ensemble/__init__.py +2 -0
  74. skfolio/optimization/ensemble/_base.py +2 -2
  75. skfolio/optimization/ensemble/_stacking.py +1 -1
  76. skfolio/optimization/naive/__init__.py +2 -0
  77. skfolio/optimization/naive/_naive.py +1 -1
  78. skfolio/population/__init__.py +2 -0
  79. skfolio/population/_population.py +34 -7
  80. skfolio/portfolio/_base.py +42 -8
  81. skfolio/portfolio/_multi_period_portfolio.py +3 -2
  82. skfolio/portfolio/_portfolio.py +4 -4
  83. skfolio/pre_selection/__init__.py +2 -0
  84. skfolio/pre_selection/_drop_correlated.py +2 -2
  85. skfolio/pre_selection/_select_complete.py +25 -26
  86. skfolio/pre_selection/_select_k_extremes.py +2 -2
  87. skfolio/pre_selection/_select_non_dominated.py +2 -2
  88. skfolio/pre_selection/_select_non_expiring.py +2 -2
  89. skfolio/preprocessing/__init__.py +2 -0
  90. skfolio/preprocessing/_returns.py +2 -2
  91. skfolio/prior/__init__.py +4 -0
  92. skfolio/prior/_base.py +2 -2
  93. skfolio/prior/_black_litterman.py +5 -3
  94. skfolio/prior/_empirical.py +3 -1
  95. skfolio/prior/_factor_model.py +8 -4
  96. skfolio/prior/_synthetic_data.py +239 -0
  97. skfolio/synthetic_returns/__init__.py +1 -0
  98. skfolio/typing.py +1 -1
  99. skfolio/uncertainty_set/__init__.py +2 -0
  100. skfolio/uncertainty_set/_base.py +2 -2
  101. skfolio/uncertainty_set/_bootstrap.py +1 -1
  102. skfolio/uncertainty_set/_empirical.py +1 -1
  103. skfolio/utils/__init__.py +1 -0
  104. skfolio/utils/bootstrap.py +2 -2
  105. skfolio/utils/equations.py +13 -10
  106. skfolio/utils/sorting.py +2 -2
  107. skfolio/utils/stats.py +7 -7
  108. skfolio/utils/tools.py +76 -12
  109. {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/METADATA +99 -24
  110. skfolio-0.8.0.dist-info/RECORD +120 -0
  111. {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/WHEEL +1 -1
  112. skfolio-0.7.0.dist-info/RECORD +0 -95
  113. {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info/licenses}/LICENSE +0 -0
  114. {skfolio-0.7.0.dist-info → skfolio-0.8.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ """Population module."""
2
+
1
3
  from skfolio.population._population import Population
2
4
 
3
5
  __all__ = ["Population"]
@@ -4,7 +4,7 @@ A population is a collection of portfolios.
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
 
9
9
  import inspect
10
10
  from typing import Any
@@ -14,6 +14,7 @@ import pandas as pd
14
14
  import plotly.express as px
15
15
  import plotly.graph_objects as go
16
16
  import scipy.interpolate as sci
17
+ import scipy.stats as st
17
18
 
18
19
  import skfolio.typing as skt
19
20
  from skfolio.measures import RatioMeasure
@@ -285,7 +286,7 @@ class Population(list):
285
286
  measure: skt.Measure,
286
287
  q: float,
287
288
  ) -> BasePortfolio:
288
- """Returns the portfolio corresponding to the `q` quantile for a given portfolio
289
+ """Return the portfolio corresponding to the `q` quantile for a given portfolio
289
290
  measure.
290
291
 
291
292
  Parameters
@@ -311,7 +312,7 @@ class Population(list):
311
312
  self,
312
313
  measure: skt.Measure,
313
314
  ) -> BasePortfolio:
314
- """Returns the portfolio with the minimum measure.
315
+ """Return the portfolio with the minimum measure.
315
316
 
316
317
  Parameters
317
318
  ----------
@@ -329,7 +330,7 @@ class Population(list):
329
330
  self,
330
331
  measure: skt.Measure,
331
332
  ) -> BasePortfolio:
332
- """Returns the portfolio with the maximum measure.
333
+ """Return the portfolio with the maximum measure.
333
334
 
334
335
  Parameters
335
336
  ----------
@@ -347,7 +348,7 @@ class Population(list):
347
348
  self,
348
349
  formatted: bool = True,
349
350
  ) -> pd.DataFrame:
350
- """Summary of the portfolios in the population
351
+ """Summary of the portfolios in the population.
351
352
 
352
353
  Parameters
353
354
  ----------
@@ -361,7 +362,6 @@ class Population(list):
361
362
  summary : pandas DataFrame
362
363
  The population's portfolios summary
363
364
  """
364
-
365
365
  df = pd.concat(
366
366
  [p.summary(formatted=formatted) for p in self],
367
367
  keys=[p.name for p in self],
@@ -473,7 +473,6 @@ class Population(list):
473
473
  dataframe : pandas DataFrame
474
474
  The rolling measures.
475
475
  """
476
-
477
476
  rolling_measures = []
478
477
  names = []
479
478
  for ptf in self:
@@ -942,6 +941,34 @@ class Population(list):
942
941
  )
943
942
  return fig
944
943
 
944
+ def plot_returns_distribution(self) -> go.Figure:
945
+ """Plot the Portfolios returns distribution using Gaussian KDE.
946
+
947
+ Returns
948
+ -------
949
+ plot : Figure
950
+ Returns the plot Figure object
951
+ """
952
+ traces = []
953
+ for ptf in self:
954
+ lower = np.percentile(ptf.returns, 1e-1)
955
+ upper = np.percentile(ptf.returns, 100 - 1e-1)
956
+ x = np.linspace(lower, upper, 500)
957
+ y = st.gaussian_kde(ptf.returns)(x)
958
+ traces.append(
959
+ go.Scatter(x=x, y=y, mode="lines", fill="tozeroy", name=ptf.name)
960
+ )
961
+ fig = go.Figure(traces)
962
+ fig.update_layout(
963
+ title="Returns Distribution",
964
+ xaxis_title="Returns",
965
+ yaxis_title="Probability Density",
966
+ )
967
+ fig.update_xaxes(
968
+ tickformat=".0%",
969
+ )
970
+ return fig
971
+
945
972
 
946
973
  def _ptf_name_with_tag(portfolio: BasePortfolio) -> str:
947
974
  if portfolio.tag is None:
@@ -1,8 +1,8 @@
1
- """Base Portfolio module"""
1
+ """Base Portfolio module."""
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
  # The Portfolio class contains more than 40 measures than can be computationally
8
8
  # expensive. The use of __slots__ instead of __dict__ is based on the following
@@ -45,6 +45,7 @@ import numpy as np
45
45
  import pandas as pd
46
46
  import plotly.express as px
47
47
  import plotly.graph_objects as go
48
+ import scipy.stats as st
48
49
 
49
50
  import skfolio.typing as skt
50
51
  from skfolio import measures as mt
@@ -613,14 +614,14 @@ class BasePortfolio:
613
614
  @property
614
615
  @abstractmethod
615
616
  def composition(self) -> pd.DataFrame:
616
- """DataFrame of the Portfolio composition"""
617
+ """DataFrame of the Portfolio composition."""
617
618
  pass
618
619
 
619
620
  @abstractmethod
620
621
  def contribution(
621
622
  self, measure: skt.Measure, spacing: float | None = None, to_df: bool = True
622
623
  ) -> np.ndarray | pd.DataFrame:
623
- """Compute the contribution of each asset to a given measure"""
624
+ """Compute the contribution of each asset to a given measure."""
624
625
  pass
625
626
 
626
627
  # Custom attribute setter and getter
@@ -654,7 +655,7 @@ class BasePortfolio:
654
655
  # Custom attribute getter (read-only and cached)
655
656
  @cached_property_slots
656
657
  def fitness(self) -> np.ndarray:
657
- """The Portfolio fitness."""
658
+ """Portfolio fitness."""
658
659
  res = []
659
660
  for measure in self.fitness_measures:
660
661
  if isinstance(measure, PerfMeasure | RatioMeasure):
@@ -679,7 +680,7 @@ class BasePortfolio:
679
680
  # Classic property
680
681
  @property
681
682
  def n_observations(self) -> int:
682
- """Number of observations"""
683
+ """Number of observations."""
683
684
  return len(self.observations)
684
685
 
685
686
  @property
@@ -709,7 +710,7 @@ class BasePortfolio:
709
710
  return self.__copy__()
710
711
 
711
712
  def clear(self) -> None:
712
- """Clear all measures, fitness, cumulative returns and drawdowns in slots"""
713
+ """Clear all measures, fitness, cumulative returns and drawdowns in slots."""
713
714
  attrs = ["_fitness", "_cumulative_returns", "_drawdowns"]
714
715
  for attr in attrs + list(_MEASURES_VALUES):
715
716
  delattr(self, attr)
@@ -1007,7 +1008,7 @@ class BasePortfolio:
1007
1008
  return fig
1008
1009
 
1009
1010
  def plot_returns(self, idx: slice | np.ndarray | None = None) -> go.Figure:
1010
- """Plot the Portfolio returns
1011
+ """Plot the Portfolio returns.
1011
1012
 
1012
1013
  Parameters
1013
1014
  ----------
@@ -1031,6 +1032,39 @@ class BasePortfolio:
1031
1032
  )
1032
1033
  return fig
1033
1034
 
1035
+ def plot_returns_distribution(self) -> go.Figure:
1036
+ """Plot the Portfolio returns distribution using Gaussian KDE.
1037
+
1038
+ Returns
1039
+ -------
1040
+ plot : Figure
1041
+ Returns the plot Figure object
1042
+ """
1043
+ lower = np.percentile(self.returns, 1e-1)
1044
+ upper = np.percentile(self.returns, 100 - 1e-1)
1045
+ x = np.linspace(lower, upper, 500)
1046
+ y = st.gaussian_kde(self.returns)(x)
1047
+
1048
+ fig = go.Figure(
1049
+ go.Scatter(
1050
+ x=x,
1051
+ y=y,
1052
+ mode="lines",
1053
+ fill="tozeroy",
1054
+ )
1055
+ )
1056
+
1057
+ fig.update_layout(
1058
+ title="Returns Distribution",
1059
+ xaxis_title="Returns",
1060
+ yaxis_title="Probability Density",
1061
+ showlegend=False,
1062
+ )
1063
+ fig.update_xaxes(
1064
+ tickformat=".0%",
1065
+ )
1066
+ return fig
1067
+
1034
1068
  def plot_rolling_measure(
1035
1069
  self,
1036
1070
  measure: skt.Measure = RatioMeasure.SHARPE_RATIO,
@@ -5,7 +5,7 @@
5
5
 
6
6
  # Copyright (c) 2023
7
7
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
8
- # License: BSD 3 clause
8
+ # SPDX-License-Identifier: BSD-3-Clause
9
9
 
10
10
  import numbers
11
11
  from collections.abc import Iterator
@@ -538,7 +538,8 @@ class MultiPeriodPortfolio(BasePortfolio):
538
538
  @portfolios.setter
539
539
  def portfolios(self, value: list[Portfolio] | None = None):
540
540
  """Set the list of Portfolios and clear the attributes cache linked to the
541
- list of portfolios."""
541
+ list of portfolios.
542
+ """
542
543
  self._set_portfolios(portfolios=value)
543
544
  self.clear()
544
545
 
@@ -6,7 +6,7 @@ is the dot product of the assets weights with the assets returns.
6
6
 
7
7
  # Copyright (c) 2023
8
8
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
9
- # License: BSD 3 clause
9
+ # SPDX-License-Identifier: BSD-3-Clause
10
10
 
11
11
  import numbers
12
12
  from typing import ClassVar
@@ -649,12 +649,12 @@ class Portfolio(BasePortfolio):
649
649
  # Custom attribute getter (read-only and cached)
650
650
  @cached_property_slots
651
651
  def nonzero_assets(self) -> np.ndarray:
652
- """Invested asset :math:`abs(weights) > 0.001%`"""
652
+ """Invested asset :math:`abs(weights) > 0.001%`."""
653
653
  return self.assets[self.nonzero_assets_index]
654
654
 
655
655
  @cached_property_slots
656
656
  def nonzero_assets_index(self) -> np.ndarray:
657
- """Indices of invested asset :math:`abs(weights) > 0.001%`"""
657
+ """Indices of invested asset :math:`abs(weights) > 0.001%`."""
658
658
  return np.flatnonzero(abs(self.weights) > _ZERO_THRESHOLD)
659
659
 
660
660
  @property
@@ -706,7 +706,7 @@ class Portfolio(BasePortfolio):
706
706
  @property
707
707
  def effective_number_assets(self) -> float:
708
708
  r"""Computes the effective number of assets, defined as the inverse of the
709
- Herfindahl index [1]_:
709
+ Herfindahl index.
710
710
 
711
711
  .. math:: N_{eff} = \frac{1}{\Vert w \Vert_{2}^{2}}
712
712
 
@@ -1,3 +1,5 @@
1
+ """Pre Selection module."""
2
+
1
3
  from skfolio.pre_selection._drop_correlated import DropCorrelated
2
4
  from skfolio.pre_selection._select_complete import SelectComplete
3
5
  from skfolio.pre_selection._select_k_extremes import SelectKExtremes
@@ -1,8 +1,8 @@
1
- """Pre-selection DropCorrelated module"""
1
+ """Pre-selection DropCorrelated module."""
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
@@ -1,8 +1,8 @@
1
- """pre-selection SelectComplete module"""
1
+ """pre-selection SelectComplete module."""
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
@@ -49,30 +49,29 @@ class SelectComplete(skf.SelectorMixin, skb.BaseEstimator):
49
49
 
50
50
  Examples
51
51
  --------
52
-
53
- >>> import numpy as np
54
- >>> import pandas as pd
55
- >>> from skfolio.pre_selection import SelectComplete
56
- >>> X = pd.DataFrame({
57
- ... 'asset1': [np.nan, np.nan, 2, 3, 4], # Starts late (inception)
58
- ... 'asset2': [1, 2, 3, 4, 5], # Complete data
59
- ... 'asset3': [1, 2, 3, np.nan, 5], # Missing values within data
60
- ... 'asset4': [1, 2, 3, 4, np.nan] # Ends early (expiration)
61
- ... })
62
- >>> selector = SelectComplete()
63
- >>> selector.fit_transform(X)
64
- array([[ 1., 1.],
65
- [ 2., 2.],
66
- [ 3., 3.],
67
- [ 4., nan],
68
- [ 5., 5.]])
69
- >>> selector = SelectComplete(drop_assets_with_internal_nan=True)
70
- >>> selector.fit_transform(X)
71
- array([[1.],
72
- [2.],
73
- [3.],
74
- [4.],
75
- [5.]])
52
+ >>> import numpy as np
53
+ >>> import pandas as pd
54
+ >>> from skfolio.pre_selection import SelectComplete
55
+ >>> X = pd.DataFrame({
56
+ ... 'asset1': [np.nan, np.nan, 2, 3, 4], # Starts late (inception)
57
+ ... 'asset2': [1, 2, 3, 4, 5], # Complete data
58
+ ... 'asset3': [1, 2, 3, np.nan, 5], # Missing values within data
59
+ ... 'asset4': [1, 2, 3, 4, np.nan] # Ends early (expiration)
60
+ ... })
61
+ >>> selector = SelectComplete()
62
+ >>> selector.fit_transform(X)
63
+ array([[ 1., 1.],
64
+ [ 2., 2.],
65
+ [ 3., 3.],
66
+ [ 4., nan],
67
+ [ 5., 5.]])
68
+ >>> selector = SelectComplete(drop_assets_with_internal_nan=True)
69
+ >>> selector.fit_transform(X)
70
+ array([[1.],
71
+ [2.],
72
+ [3.],
73
+ [4.],
74
+ [5.]])
76
75
  """
77
76
 
78
77
  to_keep_: np.ndarray
@@ -1,8 +1,8 @@
1
- """Pre-selection SelectKExtremes module"""
1
+ """Pre-selection SelectKExtremes module."""
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
@@ -1,8 +1,8 @@
1
- """Pre-selection SelectNonDominated module"""
1
+ """Pre-selection SelectNonDominated module."""
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
@@ -1,10 +1,10 @@
1
- """pre-selection estimators module"""
1
+ """pre-selection estimators module."""
2
2
 
3
3
  # Copyright (c) 2023
4
4
  # Author: Hugo Delatte <delatte.hugo@gmail.com>
5
5
  # Implementation derived from:
6
6
  # Conway-Yu https://github.com/skfolio/skfolio/discussions/60
7
- # License: BSD 3 clause
7
+ # SPDX-License-Identifier: BSD-3-Clause
8
8
 
9
9
  import datetime as dt
10
10
 
@@ -1,3 +1,5 @@
1
+ """Preprocessing module."""
2
+
1
3
  from skfolio.preprocessing._returns import prices_to_returns
2
4
 
3
5
  __all__ = ["prices_to_returns"]
@@ -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
 
7
7
  from typing import Literal
8
8
 
@@ -19,7 +19,7 @@ def prices_to_returns(
19
19
  drop_inceptions_nan: bool = True,
20
20
  fill_nan: bool = True,
21
21
  ) -> pd.DataFrame | tuple[pd.DataFrame, pd.DataFrame]:
22
- r"""Transforms a DataFrame of prices to linear or logarithmic returns.
22
+ r"""Transform a DataFrame of prices to linear or logarithmic returns.
23
23
 
24
24
  Linear returns (also called simple returns) are defined as:
25
25
  .. math:: \frac{S_{t}}{S_{t-1}} - 1
skfolio/prior/__init__.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Prior module."""
2
+
1
3
  from skfolio.prior._base import BasePrior, PriorModel
2
4
  from skfolio.prior._black_litterman import BlackLitterman
3
5
  from skfolio.prior._empirical import EmpiricalPrior
@@ -6,6 +8,7 @@ from skfolio.prior._factor_model import (
6
8
  FactorModel,
7
9
  LoadingMatrixRegression,
8
10
  )
11
+ from skfolio.prior._synthetic_data import SyntheticData
9
12
 
10
13
  __all__ = [
11
14
  "BaseLoadingMatrix",
@@ -15,4 +18,5 @@ __all__ = [
15
18
  "FactorModel",
16
19
  "LoadingMatrixRegression",
17
20
  "PriorModel",
21
+ "SyntheticData",
18
22
  ]
skfolio/prior/_base.py CHANGED
@@ -1,8 +1,8 @@
1
- """Base Prior estimator"""
1
+ """Base Prior estimator."""
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
  from abc import ABC, abstractmethod
8
8
  from dataclasses import dataclass
@@ -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
  # PyPortfolioOpt, Copyright (c) 2018 Robert Andrew Martin, Licensed under MIT Licence.
@@ -39,7 +39,7 @@ class BlackLitterman(BasePrior):
39
39
  about the assets expected returns expressed in the same frequency as the
40
40
  returns `X`.
41
41
 
42
- Examples:
42
+ For example:
43
43
 
44
44
  * "SPX = 0.00015" --> the SPX will have a daily expected return of 0.015%
45
45
  * "SX5E - TLT = 0.00039" --> the SX5E will outperform the TLT by a daily expected return of 0.039%
@@ -53,7 +53,7 @@ class BlackLitterman(BasePrior):
53
53
  (asset name/asset groups) and the input `X` of the `fit` method must be a
54
54
  DataFrame with the assets names in columns.
55
55
 
56
- Examples:
56
+ For example:
57
57
 
58
58
  * groups = {"SX5E": ["Equity", "Europe"], "SPX": ["Equity", "US"], "TLT": ["Bond", "US"]}
59
59
  * groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
@@ -119,6 +119,8 @@ class BlackLitterman(BasePrior):
119
119
  views_: np.ndarray
120
120
  picking_matrix_: np.ndarray
121
121
  prior_estimator_: BasePrior
122
+ n_features_in_: int
123
+ feature_names_in_: np.ndarray
122
124
 
123
125
  def __init__(
124
126
  self,
@@ -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
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
@@ -75,6 +75,8 @@ class EmpiricalPrior(BasePrior):
75
75
 
76
76
  mu_estimator_: BaseMu
77
77
  covariance_estimator_: BaseCovariance
78
+ n_features_in_: int
79
+ feature_names_in_: np.ndarray
78
80
 
79
81
  def __init__(
80
82
  self,
@@ -1,8 +1,8 @@
1
- """Factor Model estimator"""
1
+ """Factor Model estimator."""
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
@@ -200,6 +200,8 @@ class FactorModel(BasePrior):
200
200
 
201
201
  factor_prior_estimator_: BasePrior
202
202
  loading_matrix_estimator_: BaseLoadingMatrix
203
+ n_features_in_: int
204
+ feature_names_in_: np.ndarray
203
205
 
204
206
  def __init__(
205
207
  self,
@@ -266,6 +268,7 @@ class FactorModel(BasePrior):
266
268
  )
267
269
  factor_mu = self.factor_prior_estimator_.prior_model_.mu
268
270
  factor_covariance = self.factor_prior_estimator_.prior_model_.covariance
271
+ factor_returns = self.factor_prior_estimator_.prior_model_.returns
269
272
 
270
273
  # Fitting loading matrix estimator
271
274
  self.loading_matrix_estimator_.fit(X, y)
@@ -293,11 +296,12 @@ class FactorModel(BasePrior):
293
296
 
294
297
  mu = loading_matrix @ factor_mu + intercepts
295
298
  covariance = loading_matrix @ factor_covariance @ loading_matrix.T
296
- returns = y @ loading_matrix.T + intercepts
299
+ returns = factor_returns @ loading_matrix.T + intercepts
297
300
  cholesky = loading_matrix @ np.linalg.cholesky(factor_covariance)
298
301
 
299
302
  if self.residual_variance:
300
- err = X - returns
303
+ y_pred = y @ loading_matrix.T + intercepts
304
+ err = X - y_pred
301
305
  err_cov = np.diag(np.var(err, ddof=1, axis=0))
302
306
  covariance += err_cov
303
307
  cholesky = np.hstack((cholesky, np.sqrt(err_cov)))