skfolio 0.9.1__py3-none-any.whl → 0.10.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/distribution/multivariate/_vine_copula.py +35 -34
- skfolio/distribution/univariate/_base.py +20 -15
- skfolio/exceptions.py +5 -0
- skfolio/measures/__init__.py +2 -0
- skfolio/measures/_measures.py +390 -155
- skfolio/optimization/_base.py +21 -4
- skfolio/optimization/cluster/hierarchical/_base.py +16 -13
- skfolio/optimization/cluster/hierarchical/_herc.py +6 -6
- skfolio/optimization/cluster/hierarchical/_hrp.py +8 -6
- skfolio/optimization/convex/_base.py +238 -144
- skfolio/optimization/convex/_distributionally_robust.py +32 -20
- skfolio/optimization/convex/_maximum_diversification.py +15 -15
- skfolio/optimization/convex/_mean_risk.py +26 -24
- skfolio/optimization/convex/_risk_budgeting.py +23 -21
- skfolio/optimization/ensemble/__init__.py +2 -4
- skfolio/optimization/ensemble/_stacking.py +1 -1
- skfolio/optimization/naive/_naive.py +2 -2
- skfolio/population/_population.py +30 -9
- skfolio/portfolio/_base.py +68 -26
- skfolio/portfolio/_multi_period_portfolio.py +5 -0
- skfolio/portfolio/_portfolio.py +5 -0
- skfolio/prior/__init__.py +6 -2
- skfolio/prior/_base.py +7 -3
- skfolio/prior/_black_litterman.py +14 -12
- skfolio/prior/_empirical.py +8 -7
- skfolio/prior/_entropy_pooling.py +1493 -0
- skfolio/prior/_factor_model.py +39 -22
- skfolio/prior/_opinion_pooling.py +475 -0
- skfolio/prior/_synthetic_data.py +10 -8
- skfolio/uncertainty_set/_bootstrap.py +4 -4
- skfolio/uncertainty_set/_empirical.py +6 -6
- skfolio/utils/equations.py +10 -4
- skfolio/utils/figure.py +185 -0
- skfolio/utils/tools.py +4 -2
- {skfolio-0.9.1.dist-info → skfolio-0.10.0.dist-info}/METADATA +94 -5
- {skfolio-0.9.1.dist-info → skfolio-0.10.0.dist-info}/RECORD +40 -38
- {skfolio-0.9.1.dist-info → skfolio-0.10.0.dist-info}/WHEEL +1 -1
- skfolio/synthetic_returns/__init__.py +0 -1
- /skfolio/{optimization/ensemble/_base.py → utils/composition.py} +0 -0
- {skfolio-0.9.1.dist-info → skfolio-0.10.0.dist-info}/licenses/LICENSE +0 -0
- {skfolio-0.9.1.dist-info → skfolio-0.10.0.dist-info}/top_level.txt +0 -0
skfolio/portfolio/_base.py
CHANGED
@@ -39,6 +39,7 @@
|
|
39
39
|
|
40
40
|
import warnings
|
41
41
|
from abc import abstractmethod
|
42
|
+
from collections.abc import Callable
|
42
43
|
from typing import ClassVar
|
43
44
|
|
44
45
|
import numpy as np
|
@@ -113,6 +114,10 @@ class BasePortfolio:
|
|
113
114
|
If this is set to True, cumulative returns are compounded.
|
114
115
|
The default is `False`.
|
115
116
|
|
117
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
118
|
+
Sample weights for each observation. The weights must sum to one.
|
119
|
+
If None, equal weights are assumed.
|
120
|
+
|
116
121
|
min_acceptable_return : float, optional
|
117
122
|
The minimum acceptable return used to distinguish "downside" and "upside"
|
118
123
|
returns for the computation of lower partial moments:
|
@@ -376,6 +381,7 @@ class BasePortfolio:
|
|
376
381
|
"min_acceptable_return",
|
377
382
|
"compounded",
|
378
383
|
"risk_free_rate",
|
384
|
+
"sample_weight",
|
379
385
|
}
|
380
386
|
|
381
387
|
# Arguments locally used in measures computation
|
@@ -403,6 +409,7 @@ class BasePortfolio:
|
|
403
409
|
# custom getter and setter
|
404
410
|
"_fitness_measures",
|
405
411
|
"_annualized_factor",
|
412
|
+
"_sample_weight",
|
406
413
|
# custom getter (read-only and cached)
|
407
414
|
"_fitness",
|
408
415
|
"_cumulative_returns",
|
@@ -485,6 +492,7 @@ class BasePortfolio:
|
|
485
492
|
fitness_measures: list[skt.Measure] | None = None,
|
486
493
|
risk_free_rate: float = 0.0,
|
487
494
|
compounded: bool = False,
|
495
|
+
sample_weight: np.ndarray | None = None,
|
488
496
|
min_acceptable_return: float | None = None,
|
489
497
|
value_at_risk_beta: float = 0.95,
|
490
498
|
entropic_risk_measure_theta: float = 1.0,
|
@@ -497,6 +505,7 @@ class BasePortfolio:
|
|
497
505
|
):
|
498
506
|
self._loaded = False
|
499
507
|
self._annualized_factor = annualized_factor
|
508
|
+
self._sample_weight = sample_weight
|
500
509
|
self.returns = np.asarray(returns)
|
501
510
|
self.observations = np.asarray(observations)
|
502
511
|
self.risk_free_rate = risk_free_rate
|
@@ -652,6 +661,25 @@ class BasePortfolio:
|
|
652
661
|
self._annualized_factor = value
|
653
662
|
self.clear()
|
654
663
|
|
664
|
+
@property
|
665
|
+
def sample_weight(self) -> float:
|
666
|
+
"""Observations sample weights."""
|
667
|
+
return self._sample_weight
|
668
|
+
|
669
|
+
@sample_weight.setter
|
670
|
+
def sample_weight(self, value: np.ndarray | None) -> None:
|
671
|
+
if value is not None:
|
672
|
+
value = np.asarray(value)
|
673
|
+
if value.ndim != 1:
|
674
|
+
raise ValueError("sample_weight must be a 1D array.")
|
675
|
+
if len(value) != self.n_observations:
|
676
|
+
raise ValueError(
|
677
|
+
"sample_weight must have the same length as the number of observations."
|
678
|
+
)
|
679
|
+
if not np.isclose(value.sum(), 1):
|
680
|
+
raise ValueError("sample_weight must sum to one.")
|
681
|
+
self._sample_weight = value
|
682
|
+
|
655
683
|
# Custom attribute getter (read-only and cached)
|
656
684
|
@cached_property_slots
|
657
685
|
def fitness(self) -> np.ndarray:
|
@@ -738,19 +766,9 @@ class BasePortfolio:
|
|
738
766
|
# Local measures function arguments need to be defined in the class
|
739
767
|
# attributes with the argument name preceded by the measure name and
|
740
768
|
# separated by "_".
|
741
|
-
if measure.is_annualized:
|
742
|
-
func = getattr(mt, str(measure.non_annualized_measure.value))
|
743
|
-
else:
|
744
|
-
func = getattr(mt, str(measure.value))
|
745
769
|
|
746
|
-
args =
|
747
|
-
|
748
|
-
getattr(self, arg)
|
749
|
-
if arg in self._measure_global_args
|
750
|
-
else getattr(self, f"{measure.value}_{arg}")
|
751
|
-
)
|
752
|
-
for arg in args_names(func)
|
753
|
-
}
|
770
|
+
func, args = self._get_measure_func(measure=measure)
|
771
|
+
|
754
772
|
try:
|
755
773
|
value = func(**args)
|
756
774
|
if measure in [
|
@@ -844,15 +862,7 @@ class BasePortfolio:
|
|
844
862
|
risk_measure = non_annualized_measure
|
845
863
|
|
846
864
|
if risk_measure is not None:
|
847
|
-
risk_func =
|
848
|
-
risk_func_args = {
|
849
|
-
arg: (
|
850
|
-
getattr(self, arg)
|
851
|
-
if arg in self._measure_global_args
|
852
|
-
else getattr(self, f"{risk_measure.value}_{arg}")
|
853
|
-
)
|
854
|
-
for arg in args_names(risk_func)
|
855
|
-
}
|
865
|
+
risk_func, risk_func_args = self._get_measure_func(measure=risk_measure)
|
856
866
|
|
857
867
|
if "drawdowns" in risk_func_args:
|
858
868
|
del risk_func_args["drawdowns"]
|
@@ -1032,18 +1042,33 @@ class BasePortfolio:
|
|
1032
1042
|
)
|
1033
1043
|
return fig
|
1034
1044
|
|
1035
|
-
def plot_returns_distribution(
|
1045
|
+
def plot_returns_distribution(
|
1046
|
+
self, percentile_cutoff: float | None = None
|
1047
|
+
) -> go.Figure:
|
1036
1048
|
"""Plot the Portfolio returns distribution using Gaussian KDE.
|
1037
1049
|
|
1050
|
+
Parameters
|
1051
|
+
----------
|
1052
|
+
percentile_cutoff : float, default=None
|
1053
|
+
Percentile cutoff for tail truncation (percentile), in percent.
|
1054
|
+
If a float p is provided, the distribution support is truncated at the p-th
|
1055
|
+
and (100 - p)-th percentiles.
|
1056
|
+
If None, no truncation is applied (uses full min/max of returns).
|
1057
|
+
|
1038
1058
|
Returns
|
1039
1059
|
-------
|
1040
1060
|
plot : Figure
|
1041
1061
|
Returns the plot Figure object
|
1042
1062
|
"""
|
1043
|
-
|
1044
|
-
|
1063
|
+
returns = self.returns
|
1064
|
+
if percentile_cutoff is None:
|
1065
|
+
lower, upper = returns.min(), returns.max()
|
1066
|
+
else:
|
1067
|
+
lower = np.percentile(returns, percentile_cutoff)
|
1068
|
+
upper = np.percentile(returns, 100.0 - percentile_cutoff)
|
1069
|
+
|
1045
1070
|
x = np.linspace(lower, upper, 500)
|
1046
|
-
y = st.gaussian_kde(self.returns)(x)
|
1071
|
+
y = st.gaussian_kde(self.returns, weights=self.sample_weight)(x)
|
1047
1072
|
|
1048
1073
|
fig = go.Figure(
|
1049
1074
|
go.Scatter(
|
@@ -1090,7 +1115,7 @@ class BasePortfolio:
|
|
1090
1115
|
fig = rolling.plot(backend="plotly")
|
1091
1116
|
fig.add_hline(
|
1092
1117
|
y=getattr(self, measure.value),
|
1093
|
-
line_width=1,
|
1118
|
+
line_width=1.5,
|
1094
1119
|
line_dash="dash",
|
1095
1120
|
line_color="blue",
|
1096
1121
|
)
|
@@ -1163,3 +1188,20 @@ class BasePortfolio:
|
|
1163
1188
|
legend_title_text="Assets",
|
1164
1189
|
)
|
1165
1190
|
return fig
|
1191
|
+
|
1192
|
+
def _get_measure_func(self, measure: skt.Measure) -> tuple[Callable, dict]:
|
1193
|
+
"""Return the function and arguments of a given measure."""
|
1194
|
+
if measure.is_annualized:
|
1195
|
+
func = getattr(mt, str(measure.non_annualized_measure.value))
|
1196
|
+
else:
|
1197
|
+
func = getattr(mt, str(measure.value))
|
1198
|
+
|
1199
|
+
args = {}
|
1200
|
+
for arg in args_names(func):
|
1201
|
+
if arg in self._measure_global_args:
|
1202
|
+
args[arg] = getattr(self, arg)
|
1203
|
+
elif arg == "biased":
|
1204
|
+
args[arg] = False
|
1205
|
+
else:
|
1206
|
+
args[arg] = getattr(self, f"{measure.value}_{arg}")
|
1207
|
+
return func, args
|
@@ -63,6 +63,9 @@ class MultiPeriodPortfolio(BasePortfolio):
|
|
63
63
|
If this is set to True, cumulative returns are compounded.
|
64
64
|
The default is `False`.
|
65
65
|
|
66
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
67
|
+
Sample weights for each observation. If None, equal weights are assumed.
|
68
|
+
|
66
69
|
min_acceptable_return : float, optional
|
67
70
|
The minimum acceptable return used to distinguish "downside" and "upside"
|
68
71
|
returns for the computation of lower partial moments:
|
@@ -334,6 +337,7 @@ class MultiPeriodPortfolio(BasePortfolio):
|
|
334
337
|
annualized_factor: float = 252.0,
|
335
338
|
fitness_measures: list[skt.Measure] | None = None,
|
336
339
|
compounded: bool = False,
|
340
|
+
sample_weight: np.ndarray | None = None,
|
337
341
|
min_acceptable_return: float | None = None,
|
338
342
|
value_at_risk_beta: float = 0.95,
|
339
343
|
entropic_risk_measure_theta: float = 1,
|
@@ -354,6 +358,7 @@ class MultiPeriodPortfolio(BasePortfolio):
|
|
354
358
|
annualized_factor=annualized_factor,
|
355
359
|
fitness_measures=fitness_measures,
|
356
360
|
compounded=compounded,
|
361
|
+
sample_weight=sample_weight,
|
357
362
|
min_acceptable_return=min_acceptable_return,
|
358
363
|
value_at_risk_beta=value_at_risk_beta,
|
359
364
|
cvar_beta=cvar_beta,
|
skfolio/portfolio/_portfolio.py
CHANGED
@@ -147,6 +147,9 @@ class Portfolio(BasePortfolio):
|
|
147
147
|
If this is set to True, cumulative returns are compounded.
|
148
148
|
The default is `False`.
|
149
149
|
|
150
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
151
|
+
Sample weights for each observation. If None, equal weights are assumed.
|
152
|
+
|
150
153
|
min_acceptable_return : float, optional
|
151
154
|
The minimum acceptable return used to distinguish "downside" and "upside"
|
152
155
|
returns for the computation of lower partial moments:
|
@@ -442,6 +445,7 @@ class Portfolio(BasePortfolio):
|
|
442
445
|
annualized_factor: float = 252,
|
443
446
|
fitness_measures: list[skt.Measure] | None = None,
|
444
447
|
compounded: bool = False,
|
448
|
+
sample_weight: np.ndarray | None = None,
|
445
449
|
min_acceptable_return: float | None = None,
|
446
450
|
value_at_risk_beta: float = 0.95,
|
447
451
|
entropic_risk_measure_theta: float = 1,
|
@@ -541,6 +545,7 @@ class Portfolio(BasePortfolio):
|
|
541
545
|
tag=tag,
|
542
546
|
fitness_measures=fitness_measures,
|
543
547
|
compounded=compounded,
|
548
|
+
sample_weight=sample_weight,
|
544
549
|
risk_free_rate=risk_free_rate,
|
545
550
|
annualized_factor=annualized_factor,
|
546
551
|
min_acceptable_return=min_acceptable_return,
|
skfolio/prior/__init__.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
"""Prior module."""
|
2
2
|
|
3
|
-
from skfolio.prior._base import BasePrior,
|
3
|
+
from skfolio.prior._base import BasePrior, ReturnDistribution
|
4
4
|
from skfolio.prior._black_litterman import BlackLitterman
|
5
5
|
from skfolio.prior._empirical import EmpiricalPrior
|
6
|
+
from skfolio.prior._entropy_pooling import EntropyPooling
|
6
7
|
from skfolio.prior._factor_model import (
|
7
8
|
BaseLoadingMatrix,
|
8
9
|
FactorModel,
|
9
10
|
LoadingMatrixRegression,
|
10
11
|
)
|
12
|
+
from skfolio.prior._opinion_pooling import OpinionPooling
|
11
13
|
from skfolio.prior._synthetic_data import SyntheticData
|
12
14
|
|
13
15
|
__all__ = [
|
@@ -15,8 +17,10 @@ __all__ = [
|
|
15
17
|
"BasePrior",
|
16
18
|
"BlackLitterman",
|
17
19
|
"EmpiricalPrior",
|
20
|
+
"EntropyPooling",
|
18
21
|
"FactorModel",
|
19
22
|
"LoadingMatrixRegression",
|
20
|
-
"
|
23
|
+
"OpinionPooling",
|
24
|
+
"ReturnDistribution",
|
21
25
|
"SyntheticData",
|
22
26
|
]
|
skfolio/prior/_base.py
CHANGED
@@ -15,8 +15,8 @@ import sklearn.base as skb
|
|
15
15
|
# frozen=True with eq=False will lead to an id-based hashing which is needed for
|
16
16
|
# caching CVX models in Optimization without impacting performance
|
17
17
|
@dataclass(frozen=True, eq=False)
|
18
|
-
class
|
19
|
-
"""
|
18
|
+
class ReturnDistribution:
|
19
|
+
"""Return Distribution dataclass used by the optimization estimators.
|
20
20
|
|
21
21
|
Attributes
|
22
22
|
----------
|
@@ -36,12 +36,16 @@ class PriorModel:
|
|
36
36
|
(for example in Factor Models). When provided, this cholesky factor is use in
|
37
37
|
some optimizations (for example in mean-variance) to improve performance and
|
38
38
|
convergence. The default is `None`.
|
39
|
+
|
40
|
+
sample_weight : ndarray of shape (n_observations,), optional
|
41
|
+
Sample weights for each observation. If None, equal weights are assumed.
|
39
42
|
"""
|
40
43
|
|
41
44
|
mu: np.ndarray
|
42
45
|
covariance: np.ndarray
|
43
46
|
returns: np.ndarray
|
44
47
|
cholesky: np.ndarray | None = None
|
48
|
+
sample_weight: np.ndarray | None = None
|
45
49
|
|
46
50
|
|
47
51
|
class BasePrior(skb.BaseEstimator, ABC):
|
@@ -54,7 +58,7 @@ class BasePrior(skb.BaseEstimator, ABC):
|
|
54
58
|
arguments (no ``*args`` or ``**kwargs``).
|
55
59
|
"""
|
56
60
|
|
57
|
-
|
61
|
+
return_distribution_: ReturnDistribution
|
58
62
|
|
59
63
|
@abstractmethod
|
60
64
|
def __init__(self):
|
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Black & Litterman
|
1
|
+
"""Black & Litterman estimator."""
|
2
2
|
|
3
3
|
# Copyright (c) 2023
|
4
4
|
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
@@ -13,14 +13,14 @@ import sklearn.utils.metadata_routing as skm
|
|
13
13
|
import sklearn.utils.validation as skv
|
14
14
|
|
15
15
|
from skfolio.moments import EquilibriumMu
|
16
|
-
from skfolio.prior._base import BasePrior,
|
16
|
+
from skfolio.prior._base import BasePrior, ReturnDistribution
|
17
17
|
from skfolio.prior._empirical import EmpiricalPrior
|
18
18
|
from skfolio.utils.equations import equations_to_matrix
|
19
19
|
from skfolio.utils.tools import check_estimator, input_to_array
|
20
20
|
|
21
21
|
|
22
22
|
class BlackLitterman(BasePrior):
|
23
|
-
"""Black & Litterman
|
23
|
+
"""Black & Litterman estimator.
|
24
24
|
|
25
25
|
The Black & Litterman model [1]_ takes a Bayesian approach by using a prior estimate
|
26
26
|
of the assets expected returns and covariance matrix, which are updated using the
|
@@ -59,9 +59,9 @@ class BlackLitterman(BasePrior):
|
|
59
59
|
* groups = [["Equity", "Equity", "Bond"], ["Europe", "US", "US"]]
|
60
60
|
|
61
61
|
prior_estimator : BasePrior, optional
|
62
|
-
The assets' :ref:`prior
|
63
|
-
the :class:`~skfolio.prior.
|
64
|
-
expected returns, covariance matrix, returns and Cholesky decomposition.
|
62
|
+
The assets' :ref:`prior estimator <prior>`. It is used to estimate
|
63
|
+
the :class:`~skfolio.prior.ReturnDistribution` containing the estimation of the
|
64
|
+
assets expected returns, covariance matrix, returns and Cholesky decomposition.
|
65
65
|
The default (`None`) is to use `EmpiricalPrior(mu_estimator=EquilibriumMu())`.
|
66
66
|
|
67
67
|
tau : float, default=0.05
|
@@ -81,8 +81,10 @@ class BlackLitterman(BasePrior):
|
|
81
81
|
|
82
82
|
Attributes
|
83
83
|
----------
|
84
|
-
|
85
|
-
|
84
|
+
return_distribution_ : ReturnDistribution
|
85
|
+
Fitted :class:`~skfolio.prior.ReturnDistribution` to be used by the optimization
|
86
|
+
estimators, containing the asset returns distribution and posterior Black &
|
87
|
+
Litterman moments estimation.
|
86
88
|
|
87
89
|
groups_ : ndarray of shape(n_groups, n_assets)
|
88
90
|
Assets names and groups converted to an 2D array.
|
@@ -179,9 +181,9 @@ class BlackLitterman(BasePrior):
|
|
179
181
|
# fitting prior estimator
|
180
182
|
self.prior_estimator_.fit(X, y, **routed_params.prior_estimator.fit)
|
181
183
|
|
182
|
-
prior_mu = self.prior_estimator_.
|
183
|
-
prior_covariance = self.prior_estimator_.
|
184
|
-
prior_returns = self.prior_estimator_.
|
184
|
+
prior_mu = self.prior_estimator_.return_distribution_.mu
|
185
|
+
prior_covariance = self.prior_estimator_.return_distribution_.covariance
|
186
|
+
prior_returns = self.prior_estimator_.return_distribution_.returns
|
185
187
|
|
186
188
|
# we validate after all models have been fitted to keep features names
|
187
189
|
# information.
|
@@ -260,7 +262,7 @@ class BlackLitterman(BasePrior):
|
|
260
262
|
+ self.tau * prior_covariance
|
261
263
|
- _v @ np.linalg.solve(_a, _v.T)
|
262
264
|
)
|
263
|
-
self.
|
265
|
+
self.return_distribution_ = ReturnDistribution(
|
264
266
|
mu=posterior_mu, covariance=posterior_covariance, returns=prior_returns
|
265
267
|
)
|
266
268
|
return self
|
skfolio/prior/_empirical.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Empirical Prior
|
1
|
+
"""Empirical Prior estimator."""
|
2
2
|
|
3
3
|
# Copyright (c) 2023
|
4
4
|
# Author: Hugo Delatte <delatte.hugo@gmail.com>
|
@@ -10,15 +10,15 @@ import sklearn.utils.metadata_routing as skm
|
|
10
10
|
import sklearn.utils.validation as skv
|
11
11
|
|
12
12
|
from skfolio.moments import BaseCovariance, BaseMu, EmpiricalCovariance, EmpiricalMu
|
13
|
-
from skfolio.prior._base import BasePrior,
|
13
|
+
from skfolio.prior._base import BasePrior, ReturnDistribution
|
14
14
|
from skfolio.utils.tools import check_estimator
|
15
15
|
|
16
16
|
|
17
17
|
class EmpiricalPrior(BasePrior):
|
18
18
|
"""Empirical Prior estimator.
|
19
19
|
|
20
|
-
The Empirical Prior estimates the :class:`~skfolio.prior.
|
21
|
-
`mu_estimator` and a `covariance_estimator` separately.
|
20
|
+
The Empirical Prior estimates the :class:`~skfolio.prior.ReturnDistribution` by
|
21
|
+
fitting a `mu_estimator` and a `covariance_estimator` separately.
|
22
22
|
|
23
23
|
Parameters
|
24
24
|
----------
|
@@ -50,8 +50,9 @@ class EmpiricalPrior(BasePrior):
|
|
50
50
|
|
51
51
|
Attributes
|
52
52
|
----------
|
53
|
-
|
54
|
-
|
53
|
+
return_distribution_ : ReturnDistribution
|
54
|
+
Fitted :class:`~skfolio.prior.ReturnDistribution` to be used by the optimization
|
55
|
+
estimators, containing the asset returns distribution and moments estimation.
|
55
56
|
|
56
57
|
mu_estimator_ : BaseMu
|
57
58
|
Fitted `mu_estimator`.
|
@@ -194,7 +195,7 @@ class EmpiricalPrior(BasePrior):
|
|
194
195
|
# we validate and convert to numpy after all models have been fitted to keep
|
195
196
|
# features names information.
|
196
197
|
X = skv.validate_data(self, X)
|
197
|
-
self.
|
198
|
+
self.return_distribution_ = ReturnDistribution(
|
198
199
|
mu=mu,
|
199
200
|
covariance=covariance,
|
200
201
|
returns=X,
|