skfolio 0.7.0__py3-none-any.whl → 0.8.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.
- skfolio/__init__.py +2 -2
- skfolio/cluster/__init__.py +1 -1
- skfolio/cluster/_hierarchical.py +1 -1
- skfolio/datasets/__init__.py +1 -1
- skfolio/datasets/_base.py +2 -2
- skfolio/datasets/data/__init__.py +1 -0
- skfolio/distance/__init__.py +1 -1
- skfolio/distance/_base.py +2 -2
- skfolio/distance/_distance.py +4 -4
- skfolio/distribution/__init__.py +56 -0
- skfolio/distribution/_base.py +203 -0
- skfolio/distribution/copula/__init__.py +35 -0
- skfolio/distribution/copula/_base.py +456 -0
- skfolio/distribution/copula/_clayton.py +539 -0
- skfolio/distribution/copula/_gaussian.py +407 -0
- skfolio/distribution/copula/_gumbel.py +560 -0
- skfolio/distribution/copula/_independent.py +196 -0
- skfolio/distribution/copula/_joe.py +609 -0
- skfolio/distribution/copula/_selection.py +111 -0
- skfolio/distribution/copula/_student_t.py +486 -0
- skfolio/distribution/copula/_utils.py +509 -0
- skfolio/distribution/multivariate/__init__.py +11 -0
- skfolio/distribution/multivariate/_base.py +241 -0
- skfolio/distribution/multivariate/_utils.py +632 -0
- skfolio/distribution/multivariate/_vine_copula.py +1254 -0
- skfolio/distribution/univariate/__init__.py +19 -0
- skfolio/distribution/univariate/_base.py +308 -0
- skfolio/distribution/univariate/_gaussian.py +136 -0
- skfolio/distribution/univariate/_johnson_su.py +152 -0
- skfolio/distribution/univariate/_normal_inverse_gaussian.py +153 -0
- skfolio/distribution/univariate/_selection.py +85 -0
- skfolio/distribution/univariate/_student_t.py +144 -0
- skfolio/exceptions.py +6 -6
- skfolio/measures/__init__.py +1 -1
- skfolio/measures/_enums.py +7 -7
- skfolio/measures/_measures.py +4 -7
- skfolio/metrics/__init__.py +2 -0
- skfolio/metrics/_scorer.py +4 -4
- skfolio/model_selection/__init__.py +2 -2
- skfolio/model_selection/_combinatorial.py +15 -12
- skfolio/model_selection/_validation.py +2 -2
- skfolio/model_selection/_walk_forward.py +3 -3
- skfolio/moments/covariance/_base.py +1 -1
- skfolio/moments/covariance/_denoise_covariance.py +1 -1
- skfolio/moments/covariance/_detone_covariance.py +1 -1
- skfolio/moments/covariance/_empirical_covariance.py +1 -1
- skfolio/moments/covariance/_ew_covariance.py +1 -1
- skfolio/moments/covariance/_gerber_covariance.py +1 -1
- skfolio/moments/covariance/_graphical_lasso_cv.py +1 -1
- skfolio/moments/covariance/_implied_covariance.py +2 -7
- skfolio/moments/covariance/_ledoit_wolf.py +1 -1
- skfolio/moments/covariance/_oas.py +1 -1
- skfolio/moments/covariance/_shrunk_covariance.py +1 -1
- skfolio/moments/expected_returns/_base.py +1 -1
- skfolio/moments/expected_returns/_empirical_mu.py +1 -1
- skfolio/moments/expected_returns/_equilibrium_mu.py +1 -1
- skfolio/moments/expected_returns/_ew_mu.py +1 -1
- skfolio/moments/expected_returns/_shrunk_mu.py +2 -2
- skfolio/optimization/__init__.py +2 -0
- skfolio/optimization/_base.py +2 -2
- skfolio/optimization/cluster/__init__.py +2 -0
- skfolio/optimization/cluster/_nco.py +7 -7
- skfolio/optimization/cluster/hierarchical/__init__.py +2 -0
- skfolio/optimization/cluster/hierarchical/_base.py +1 -2
- skfolio/optimization/cluster/hierarchical/_herc.py +2 -2
- skfolio/optimization/cluster/hierarchical/_hrp.py +2 -2
- skfolio/optimization/convex/__init__.py +2 -0
- skfolio/optimization/convex/_base.py +8 -8
- skfolio/optimization/convex/_distributionally_robust.py +4 -4
- skfolio/optimization/convex/_maximum_diversification.py +5 -5
- skfolio/optimization/convex/_mean_risk.py +5 -6
- skfolio/optimization/convex/_risk_budgeting.py +3 -3
- skfolio/optimization/ensemble/__init__.py +2 -0
- skfolio/optimization/ensemble/_base.py +2 -2
- skfolio/optimization/ensemble/_stacking.py +1 -1
- skfolio/optimization/naive/__init__.py +2 -0
- skfolio/optimization/naive/_naive.py +1 -1
- skfolio/population/__init__.py +2 -0
- skfolio/population/_population.py +35 -9
- skfolio/portfolio/_base.py +42 -8
- skfolio/portfolio/_multi_period_portfolio.py +3 -2
- skfolio/portfolio/_portfolio.py +4 -4
- skfolio/pre_selection/__init__.py +2 -0
- skfolio/pre_selection/_drop_correlated.py +2 -2
- skfolio/pre_selection/_select_complete.py +25 -26
- skfolio/pre_selection/_select_k_extremes.py +2 -2
- skfolio/pre_selection/_select_non_dominated.py +2 -2
- skfolio/pre_selection/_select_non_expiring.py +2 -2
- skfolio/preprocessing/__init__.py +2 -0
- skfolio/preprocessing/_returns.py +2 -2
- skfolio/prior/__init__.py +4 -0
- skfolio/prior/_base.py +2 -2
- skfolio/prior/_black_litterman.py +5 -3
- skfolio/prior/_empirical.py +3 -1
- skfolio/prior/_factor_model.py +8 -4
- skfolio/prior/_synthetic_data.py +239 -0
- skfolio/synthetic_returns/__init__.py +1 -0
- skfolio/typing.py +1 -1
- skfolio/uncertainty_set/__init__.py +2 -0
- skfolio/uncertainty_set/_base.py +2 -2
- skfolio/uncertainty_set/_bootstrap.py +1 -1
- skfolio/uncertainty_set/_empirical.py +1 -1
- skfolio/utils/__init__.py +1 -0
- skfolio/utils/bootstrap.py +2 -2
- skfolio/utils/equations.py +13 -10
- skfolio/utils/sorting.py +2 -2
- skfolio/utils/stats.py +7 -7
- skfolio/utils/tools.py +76 -12
- {skfolio-0.7.0.dist-info → skfolio-0.8.1.dist-info}/METADATA +99 -24
- skfolio-0.8.1.dist-info/RECORD +120 -0
- {skfolio-0.7.0.dist-info → skfolio-0.8.1.dist-info}/WHEEL +1 -1
- skfolio-0.7.0.dist-info/RECORD +0 -95
- {skfolio-0.7.0.dist-info → skfolio-0.8.1.dist-info/licenses}/LICENSE +0 -0
- {skfolio-0.7.0.dist-info → skfolio-0.8.1.dist-info}/top_level.txt +0 -0
skfolio/population/__init__.py
CHANGED
@@ -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
|
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
|
-
"""
|
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
|
-
"""
|
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
|
-
"""
|
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:
|
@@ -814,8 +813,7 @@ class Population(list):
|
|
814
813
|
)
|
815
814
|
+ "<extra></extra>",
|
816
815
|
colorbar=dict(
|
817
|
-
title=str(z),
|
818
|
-
titleside="top",
|
816
|
+
title=dict(text=str(z), side="top"),
|
819
817
|
tickformat=",.2%" if not z.is_ratio else None,
|
820
818
|
),
|
821
819
|
)
|
@@ -942,6 +940,34 @@ class Population(list):
|
|
942
940
|
)
|
943
941
|
return fig
|
944
942
|
|
943
|
+
def plot_returns_distribution(self) -> go.Figure:
|
944
|
+
"""Plot the Portfolios returns distribution using Gaussian KDE.
|
945
|
+
|
946
|
+
Returns
|
947
|
+
-------
|
948
|
+
plot : Figure
|
949
|
+
Returns the plot Figure object
|
950
|
+
"""
|
951
|
+
traces = []
|
952
|
+
for ptf in self:
|
953
|
+
lower = np.percentile(ptf.returns, 1e-1)
|
954
|
+
upper = np.percentile(ptf.returns, 100 - 1e-1)
|
955
|
+
x = np.linspace(lower, upper, 500)
|
956
|
+
y = st.gaussian_kde(ptf.returns)(x)
|
957
|
+
traces.append(
|
958
|
+
go.Scatter(x=x, y=y, mode="lines", fill="tozeroy", name=ptf.name)
|
959
|
+
)
|
960
|
+
fig = go.Figure(traces)
|
961
|
+
fig.update_layout(
|
962
|
+
title="Returns Distribution",
|
963
|
+
xaxis_title="Returns",
|
964
|
+
yaxis_title="Probability Density",
|
965
|
+
)
|
966
|
+
fig.update_xaxes(
|
967
|
+
tickformat=".0%",
|
968
|
+
)
|
969
|
+
return fig
|
970
|
+
|
945
971
|
|
946
972
|
def _ptf_name_with_tag(portfolio: BasePortfolio) -> str:
|
947
973
|
if portfolio.tag is None:
|
skfolio/portfolio/_base.py
CHANGED
@@ -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
|
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
|
-
"""
|
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
|
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
|
|
skfolio/portfolio/_portfolio.py
CHANGED
@@ -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
|
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
|
709
|
+
Herfindahl index.
|
710
710
|
|
711
711
|
.. math:: N_{eff} = \frac{1}{\Vert w \Vert_{2}^{2}}
|
712
712
|
|
@@ -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
|
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
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
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
|
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
|
7
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
8
8
|
|
9
9
|
import datetime as dt
|
10
10
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Copyright (c) 2023
|
4
4
|
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
-
# License: BSD
|
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"""
|
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
|
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
|
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
|
-
|
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
|
-
|
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,
|
skfolio/prior/_empirical.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# Copyright (c) 2023
|
4
4
|
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
5
|
-
# License: BSD
|
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,
|
skfolio/prior/_factor_model.py
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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)))
|