Portfolio#
Portfolio classes implement a large set of attributes and methods intended for
portfolio analysis. They are returned by the predict method of
portfolio optimizations.
They are also data-containers (calling
np.asarray(portfolio) returns the portfolio returns) making them compatible
with sklearn.model_selection tools.
They use slots for improved performance.
Base Portfolio#
BasePortfolio directly takes a portfolio returns array as input and implements
a large set of attributes and methods.
Example:
import datetime as dt
from skfolio import BasePortfolio
portfolio = BasePortfolio(
returns=[0.002, -0.001, 0.0015],
observations=[dt.date(2022, 1, 1), dt.date(2022, 1, 2), dt.date(2022, 1, 3)],
)
Attributes and Methods#
More than 40 attributes and methods are available, including all the
measures (Mean, Variance, Sharpe Ratio, CVaR, CDaR, Drawdowns,
etc.). The attributes are computed only when requested, then cached in slots for
enhanced performance.
Example:
from skfolio import RatioMeasure
# attributes
portfolio.mean
portfolio.variance
portfolio.sharpe_ratio
portfolio.sortino_ratio
portfolio.cdar
portfolio.max_drawdown
portfolio.cumulative_returns
portfolio.drawdowns
portfolio.returns_df
portfolio.cumulative_returns_df
# methods
portfolio.summary()
portfolio.dominates(other_portfolio)
portfolio.rolling_measure(measure=RatioMeasure.SHARPE_RATIO)
# plots
portfolio.plot_cumulative_returns()
portfolio.plot_rolling_measure(measure=RatioMeasure.SHARPE_RATIO)
It is also an array container:
np.asarray(portfolio)
>>> array([ 0.002 , -0.001 , 0.0015])
Finally, portfolios can be compared together using domination:
portfolio == other_portfolio
portfolio >= other_portfolio
portfolio > other_portfolio
The measures used in the domination are controlled using fitness_measures. The default
is to use the list [PerfMeasure.MEAN, RiskMeasure.VARIANCE].
Portfolio#
Portfolio inherits from BasePortfolio. The portfolio returns are the
dot product of the assets weights with the assets returns minus costs:
\[r_p = R \cdot w^{T} - c^{T} \cdot | w - w_{prev} | - f^{T} \cdot w\]
with \(r_p\) the vector of portfolio returns , \(R\) the matrix of assets returns, \(w\) the vector of assets weights, \(c\) the vector of assets transaction costs, \(f\) the vector of assets management fees and \(w_{prev}\) the assets previous weights.
Warning
The Portfolio formulation is consistent with the convex optimization
problems: portfolio returns are computed as a dot product of weights and asset
returns, minus costs. This formulation is not perfectly replicable due to weight
drift when asset prices move, except in the ideal case of periodic rebalancing with
zero transaction costs.
This design choice is analogous to using non-compounded vs compounded returns to
compare trading strategies. skfolio focuses on allocation skill, which corresponds
to an expectation-based (ex-ante) evaluation, rather than on realized capital
growth, which corresponds to a path-dependent (ex-post) evaluation along a
single return path.
Weight drift introduces path dependence: early winners get larger weights, early losers shrink, and outcomes depend on return ordering. Two portfolios with the same expected returns and covariances can end with very different performance due only to the sequence of returns, which contaminates the comparison. Likewise, a volatile asset can dominate portfolio results because it moved early, not because it has a higher expected return.
Example:
from skfolio import Portfolio
X = [
[0.003, -0.001],
[-0.001, 0.002],
[0.0015, 0.004],
]
weights = [0.6, 0.4]
portfolio = Portfolio(X=X, weights=weights)
print(portfolio.returns)
>>> array([0.0014, 0.0002, 0.0025])
X can be any data-container including numpy array and pandas DataFrame:
import datetime as dt
import pandas as pd
X = pd.DataFrame(
data=[[0.003, -0.001], [-0.001, 0.002], [0.0015, 0.004]],
columns=["Asset A", "Asset B"],
index=[dt.date(2022, 1, 1), dt.date(2022, 1, 2), dt.date(2022, 1, 3)],
)
print(X)
>>>
Asset A Asset B
2022-01-01 0.0030 -0.001
2022-01-02 -0.0010 0.002
2022-01-03 0.0015 0.004
weights = [0.6, 0.4]
portfolio = Portfolio(X=X, weights=weights, name="my_portfolio")
print(portfolio.returns)
>>> array([0.0014, 0.0002, 0.0025])
Attributes and Methods#
Portfolio inherits all the attributes and methods from BasePortfolio.
In addition, it also implements weights related methods:
from skfolio import RatioMeasure
portfolio.contribution(measure=RatioMeasure.ANNUALIZED_SHARPE_RATIO)
>>> array([-3.04203502, 3.04203503])
portfolio.composition
>>>
my_portfolio
asset
Asset A 0.6
Asset B 0.4
portfolio.get_weight("Asset A")
>>> 0.6
# Plots
portfolio.plot_contribution()
portfolio.plot_composition()
Multi Period Portfolio#
MultiPeriodPortfolio inherits from BasePortfolio and is composed of a
list of Portfolio. The multi-period portfolio returns are the sum of all its
underlying Portfolio returns.
A MultiPeriodPortfolio is returned by cross_val_predict.
For example, calling cross_val_predict with WalkForward
will return a MultiPeriodPortfolio composed of multiple test Portfolio, each
corresponding to a train/test fold.
from skfolio import MultiPeriodPortfolio
portfolio = MultiPeriodPortfolio(portfolios=[ptf1, ptf2, ptf3])