Skip to content

Commit

Permalink
Merge pull request #21 from skfolio/fix/issue-19/number-of-assets
Browse files Browse the repository at this point in the history
[BUG] Number of Assets and Portfolio len
  • Loading branch information
HugoDelatte committed Jan 25, 2024
2 parents 9c5238d + 16205d5 commit 1c8b357
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 24 deletions.
4 changes: 2 additions & 2 deletions src/skfolio/measures/_measures.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ def effective_number_assets(weights: np.ndarray) -> float:
r"""
Computes the effective number of assets, defined as the inverse of the Herfindahl index [1]_:
.. math:: N_{eff} = \frac{1}{\Vert w \Vert_{2}^{2}}
It quantifies portfolio concentration, with a higher value indicating a more diversified portfolio.
Parameters
Expand All @@ -632,4 +632,4 @@ def effective_number_assets(weights: np.ndarray) -> float:
.. [1] "Banking and Financial Institutions Law in a Nutshell".
Lovett, William Anthony (1988)
"""
return 1.0/(np.power(weights, 2).sum())
return 1.0 / (np.power(weights, 2).sum())
11 changes: 5 additions & 6 deletions src/skfolio/portfolio/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,6 @@ class BasePortfolio:
_read_only_attrs: ClassVar[set] = {
"returns",
"observations",
"n_observations",
}

# Arguments globally used in measures computation
Expand Down Expand Up @@ -401,7 +400,6 @@ class BasePortfolio:
# public read-only
"returns",
"observations",
"n_observations",
# private
"_loaded",
# custom getter and setter
Expand Down Expand Up @@ -521,7 +519,6 @@ def __init__(
self._fitness_measures = [PerfMeasure.MEAN, RiskMeasure.VARIANCE]
else:
self._fitness_measures = fitness_measures
self.n_observations = len(observations)
self._loaded = True

def __reduce__(self):
Expand All @@ -531,9 +528,6 @@ def __reduce__(self):
[getattr(self, arg) for arg in args_names(self.__init__)]
)

def __len__(self) -> int:
return len(self.observations)

def __repr__(self) -> str:
return f"<{type(self).__name__} {self.name}>"

Expand Down Expand Up @@ -679,6 +673,11 @@ def drawdowns(self) -> np.ndarray:
return mt.get_drawdowns(returns=self.returns, compounded=self.compounded)

# Classic property
@property
def n_observations(self) -> int:
"""Number of observations"""
return len(self.observations)

@property
def returns_df(self) -> pd.Series:
"""Portfolio returns DataFrame."""
Expand Down
9 changes: 6 additions & 3 deletions src/skfolio/portfolio/_multi_period_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ def __init__(
self.check_observations_order = check_observations_order
self._set_portfolios(portfolios=portfolios)

def __len__(self) -> int:
return len(self.portfolios)

def __getitem__(self, key: int | slice) -> Portfolio | list[Portfolio]:
return self._portfolios[key]

Expand Down Expand Up @@ -571,12 +574,12 @@ def summary(self, formatted: bool = True) -> pd.Series:
"""
df = super().summary(formatted=formatted)
portfolios_number = len(self)
avg_assets_per_portfolio = np.mean([len(p) for p in self])
avg_assets_per_portfolio = np.mean([p.n_assets for p in self])
if formatted:
portfolios_number = str(int(portfolios_number))
avg_assets_per_portfolio = f"{avg_assets_per_portfolio:0.1f}"
df["Portfolios number"] = portfolios_number
df["Avg nb of assets per portfolio"] = avg_assets_per_portfolio
df["Portfolios Number"] = portfolios_number
df["Avg nb of Assets per Portfolio"] = avg_assets_per_portfolio
return df

# Public methods
Expand Down
16 changes: 7 additions & 9 deletions src/skfolio/utils/fixes/_dendrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,13 @@ def set_figure_layout(self, width, height):
Sets and returns default layout object for dendrogram figure.
"""
self.layout.update(
{
"showlegend": False,
"autosize": False,
"hovermode": "closest",
"width": width,
"height": height,
}
)
self.layout.update({
"showlegend": False,
"autosize": False,
"hovermode": "closest",
"width": width,
"height": height,
})

self.set_axis_layout(self.xaxis)
self.set_axis_layout(self.yaxis)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_model_selection/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ def test_validation_combinatorial(X):
for p in pred:
assert isinstance(p, MultiPeriodPortfolio)
assert len(p.portfolios) == cv.n_folds
assert len(p) == n_observations
assert len(p) == cv.n_folds
assert p.n_observations == n_observations
10 changes: 9 additions & 1 deletion tests/test_portfolio/test_multi_period_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ def test_portfolio_annualized(portfolio_and_returns, annualized_factor):

def test_portfolio_methods(portfolio_and_returns, periods):
portfolio, returns = portfolio_and_returns
assert len(portfolio) == returns.shape[0]
assert portfolio.n_observations == returns.shape[0]
assert len(portfolio) == len(periods)
np.testing.assert_almost_equal(returns, portfolio.returns)
np.testing.assert_almost_equal(returns.mean(), portfolio.mean)
np.testing.assert_almost_equal(returns.std(ddof=1), portfolio.standard_deviation)
Expand Down Expand Up @@ -386,3 +387,10 @@ def test_portfolio_delete_attr(portfolio_and_returns, periods):
raise
except AttributeError as e:
assert str(e) == "`MultiPeriodPortfolio` object has no attribute 'dummy'"


def test_portfolio_summary(portfolio_and_returns, periods):
portfolio, returns = portfolio_and_returns
df = portfolio.summary(formatted=False)
assert df.loc["Portfolios Number"] == 3.0
assert df.loc["Avg nb of Assets per Portfolio"] == 20.0
6 changes: 4 additions & 2 deletions tests/test_portfolio/test_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ def test_portfolio_annualized(X, weights, annualized_factor):
def test_portfolio_methods(X, weights):
portfolio = Portfolio(X=X, weights=weights)
returns = _portfolio_returns(asset_returns=X.to_numpy(), weights=weights)
assert len(portfolio) == X.shape[0]
assert portfolio.n_observations == X.shape[0]
assert portfolio.n_assets == X.shape[1]
np.testing.assert_almost_equal(returns, portfolio.returns)
np.testing.assert_almost_equal(returns.mean(), portfolio.mean)
np.testing.assert_almost_equal(returns.std(ddof=1), portfolio.standard_deviation)
Expand Down Expand Up @@ -235,7 +236,8 @@ def test_portfolio_magic_methods(X, weights):
n_assets = X.shape[1]
ptf_1 = Portfolio(X=X, weights=rand_weights(n=n_assets))
ptf_2 = Portfolio(X=X, weights=rand_weights(n=n_assets))
assert len(ptf_1) == X.shape[0]
assert ptf_1.n_observations == X.shape[0]
assert ptf_1.n_assets == X.shape[1]
ptf = ptf_1 + ptf_2
assert np.array_equal(ptf.weights, ptf_1.weights + ptf_2.weights)
ptf = ptf_1 - ptf_2
Expand Down

0 comments on commit 1c8b357

Please sign in to comment.