<a id="sphx-glr-auto-examples-mean-risk-plot-13-factor-model-py"></a>
> <a id="factor-model"></a>

<a id="time-series-factor-model"></a>

<a id="factor-model"></a>

# Time-Series Factor Model

`skfolio` supports time-series factor models through
[`TimeSeriesFactorModel`](https://skfolio.org/generated/skfolio.prior.TimeSeriesFactorModel.html.md#skfolio.prior.TimeSeriesFactorModel) and characteristics-based
cross-sectional factor models through
[`CharacteristicsFactorModel`](https://skfolio.org/generated/skfolio.prior.CharacteristicsFactorModel.html.md#skfolio.prior.CharacteristicsFactorModel).

This tutorial uses [`TimeSeriesFactorModel`](https://skfolio.org/generated/skfolio.prior.TimeSeriesFactorModel.html.md#skfolio.prior.TimeSeriesFactorModel) to build a maximum
Sharpe ratio portfolio with [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk). Factor returns
are supplied as time series, and each asset’s exposures are estimated by regressing
its returns on those factor returns.

[`CharacteristicsFactorModel`](https://skfolio.org/generated/skfolio.prior.CharacteristicsFactorModel.html.md#skfolio.prior.CharacteristicsFactorModel) builds exposures from
point-in-time asset characteristics and estimates factor returns through
cross-sectional regression at each date. See the [Factor Models user guide](https://skfolio.org/user_guide/factor_models.html.md#factor-models) for the methodology and the [Characteristics Factor Model
tutorial](https://skfolio.org/auto_examples/factor_models/plot_characteristics_factor_model.html.md#sphx-glr-auto-examples-factor-models-plot-characteristics-factor-model-py)
for a complete example.

A [Prior Estimator](https://skfolio.org/user_guide/prior.html.md#prior) in `skfolio` estimates a
[`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) containing the inputs to portfolio
optimization ($\mu$, $\Sigma$, returns, sample weights and a Cholesky
decomposition).

The term “prior” is used in a general optimization sense, not confined to Bayesian
priors. It denotes any **a priori** assumption or estimation method for the return
distribution before optimization, unifying **Frequentist**, **Bayesian** and
**Information-theoretic** approaches into a single cohesive framework:

1. Frequentist:
   : * [`EmpiricalPrior`](https://skfolio.org/generated/skfolio.prior.EmpiricalPrior.html.md#skfolio.prior.EmpiricalPrior)
     * [`TimeSeriesFactorModel`](https://skfolio.org/generated/skfolio.prior.TimeSeriesFactorModel.html.md#skfolio.prior.TimeSeriesFactorModel)
     * [`CharacteristicsFactorModel`](https://skfolio.org/generated/skfolio.prior.CharacteristicsFactorModel.html.md#skfolio.prior.CharacteristicsFactorModel)
     * [`SyntheticData`](https://skfolio.org/generated/skfolio.prior.SyntheticData.html.md#skfolio.prior.SyntheticData)
2. Bayesian:
   : * [`BlackLitterman`](https://skfolio.org/generated/skfolio.prior.BlackLitterman.html.md#skfolio.prior.BlackLitterman)
3. Information-theoretic:
   : * [`EntropyPooling`](https://skfolio.org/generated/skfolio.prior.EntropyPooling.html.md#skfolio.prior.EntropyPooling)
     * [`OpinionPooling`](https://skfolio.org/generated/skfolio.prior.OpinionPooling.html.md#skfolio.prior.OpinionPooling)

Prior estimators follow scikit-learn’s estimator API and store the fitted
[`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) in `return_distribution_`. Their inputs
depend on the model. `TimeSeriesFactorModel` takes asset returns in `X` and factor
returns through the `factors` keyword argument.

The [`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) is a dataclass containing:

> * `mu`: Estimated expected returns of shape (n_assets,)
> * `covariance`: Estimated covariance matrix of shape (n_assets, n_assets)
> * `returns`: (Estimated) asset returns of shape (n_observations, n_assets)
> * `sample_weight` : Sample weight for each observation of shape (n_observations,) (optional)
> * `cholesky` : Lower-triangular Cholesky factor of the covariance (optional)

The `TimeSeriesFactorModel` estimator estimates the `ReturnDistribution` by fitting
a factor model on asset returns alongside a specified [prior estimator](https://skfolio.org/user_guide/prior.html.md#prior)
for the factor returns.

The purpose of factor models is to impose a structure on financial variables and
their covariance matrix by explaining them through a small number of common factors.
This can help overcome estimation error by reducing the number of parameters,
i.e., the dimensionality of the estimation problem, making portfolio optimization
more robust against noise in the data. Factor models also provide a decomposition of
financial risk into systematic and security-specific components.

<a id="data"></a>

## Data

We load the S&P 500 [dataset](https://skfolio.org/user_guide/datasets.html.md#datasets) composed of the daily prices of 20
assets from the SPX Index composition and the Factors dataset composed of the daily
prices of 5 ETFs representing common factors:

```Python
from plotly.io import show
from sklearn import set_config
from sklearn.linear_model import RidgeCV
from sklearn.model_selection import train_test_split

from skfolio import Population, RiskMeasure
from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.moments import GerberCovariance, ShrunkMu
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import EmpiricalPrior, LoadingMatrixRegression, TimeSeriesFactorModel

set_config(enable_metadata_routing=True)

prices = load_sp500_dataset()
factor_prices = load_factors_dataset()

X, factors = prices_to_returns(prices, factor_prices)
X_train, X_test, factors_train, factors_test = train_test_split(
    X, factors, test_size=0.33, shuffle=False
)
```

<a id="id1"></a>

<a id="id2"></a>

## Time-Series Factor Model

We create a Maximum Sharpe Ratio model using `TimeSeriesFactorModel` and fit it
on the training set:

```Python
model_factor_1 = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    prior_estimator=TimeSeriesFactorModel(),
    portfolio_params=dict(name="Factor Model 1"),
)
model_factor_1.fit(X_train, factors=factors_train)
model_factor_1.weights_
```

```none
array([1.03294289e-06, 1.27482685e-03, 4.19682804e-07, 3.34130826e-06,
       7.36838288e-07, 1.28824408e-06, 5.13031432e-02, 6.35619183e-02,
       6.14804834e-07, 1.79106051e-01, 5.03130911e-02, 7.13734379e-02,
       4.13002526e-02, 2.27978407e-01, 5.13348034e-02, 1.44130375e-01,
       2.99026117e-07, 6.19737850e-02, 5.63413085e-02, 8.67773198e-07])
```

We can change the [`BaseLoadingMatrix`](https://skfolio.org/generated/skfolio.prior.BaseLoadingMatrix.html.md#skfolio.prior.BaseLoadingMatrix) that estimates the loading
matrix (betas) of the factors.

The default is the `LoadingMatrixRegression`, which fit the factors using a
`LassoCV` on each asset separately.

For example, let’s change the `LassoCV` into a `RidgeCV` without intercept and use
parallelization:

```Python
model_factor_2 = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    prior_estimator=TimeSeriesFactorModel(
        loading_matrix_estimator=LoadingMatrixRegression(
            linear_regressor=RidgeCV(fit_intercept=False), n_jobs=-1
        )
    ),
    portfolio_params=dict(name="Factor Model 2"),
)
model_factor_2.fit(X_train, factors=factors_train)
model_factor_2.weights_
```

```none
array([3.97758339e-02, 6.57843874e-03, 2.18405141e-02, 8.98258882e-03,
       3.16197378e-02, 1.42391168e-02, 8.00124906e-02, 8.32090802e-02,
       4.74782930e-02, 8.59470407e-02, 4.59776221e-02, 5.91778878e-02,
       8.42236770e-02, 1.05684777e-01, 6.43841778e-02, 7.94729901e-02,
       3.76786712e-05, 5.23695742e-02, 4.35215146e-02, 4.54669667e-02])
```

We can also change the [prior estimator](https://skfolio.org/user_guide/prior.html.md#prior) of the factors.
It is used to estimate the [`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) containing
expected factor returns and the factor covariance matrix.

For example, let’s estimate expected factor returns with James-Stein shrinkage
and the factor covariance matrix with the Gerber covariance estimator:

```Python
model_factor_3 = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    prior_estimator=TimeSeriesFactorModel(
        factor_prior_estimator=EmpiricalPrior(
            mu_estimator=ShrunkMu(), covariance_estimator=GerberCovariance()
        )
    ),
    portfolio_params=dict(name="Factor Model 3"),
)
model_factor_3.fit(X_train, factors=factors_train)
model_factor_3.weights_
```

```none
array([4.86490691e-07, 4.38230194e-07, 4.24408222e-08, 6.69653314e-08,
       5.11878214e-08, 6.14581602e-08, 1.68436387e-02, 2.08439609e-06,
       5.27854154e-08, 6.45513596e-02, 6.24004728e-02, 9.61498232e-02,
       3.68209826e-01, 2.44692220e-01, 5.86512138e-07, 9.30385165e-06,
       2.19939735e-08, 1.47139096e-01, 3.09340379e-07, 5.81316432e-08])
```

<a id="factor-analysis"></a>

## Factor Analysis

Each fitted estimator is saved with a trailing underscore.
For example, we can access the fitted prior estimator with:

```Python
prior_estimator = model_factor_3.prior_estimator_
```

We can access the return distribution with:

```Python
return_distribution = prior_estimator.return_distribution_
```

We can access the loading matrix with:

```Python
loading_matrix = prior_estimator.loading_matrix_estimator_.loading_matrix_
```

<a id="empirical-model"></a>

## Empirical Model

For comparison, we also create a Maximum Sharpe Ratio model using the default
Empirical estimator:

```Python
model_empirical = MeanRisk(
    risk_measure=RiskMeasure.VARIANCE,
    objective_function=ObjectiveFunction.MAXIMIZE_RATIO,
    portfolio_params=dict(name="Empirical"),
)
model_empirical.fit(X_train)
model_empirical.weights_
```

```none
array([1.01561518e-01, 7.81165193e-02, 6.29030035e-07, 1.89005488e-02,
       3.05610118e-07, 1.55770502e-07, 1.10594710e-01, 1.22328443e-06,
       1.56471742e-06, 3.39453275e-06, 1.62631058e-01, 1.92171374e-06,
       1.77783711e-01, 9.61805760e-02, 4.64493061e-07, 9.68566446e-03,
       7.41771351e-08, 2.44533886e-01, 1.83984286e-06, 2.34178300e-07])
```

<a id="prediction"></a>

## Prediction

We predict all models on the test set:

```Python
ptf_factor_1_test = model_factor_1.predict(X_test)
ptf_factor_2_test = model_factor_2.predict(X_test)
ptf_factor_3_test = model_factor_3.predict(X_test)
ptf_empirical_test = model_empirical.predict(X_test)

population = Population(
    [ptf_factor_1_test, ptf_factor_2_test, ptf_factor_3_test, ptf_empirical_test]
)

fig = population.plot_cumulative_returns()
show(fig)
```

[plotly figure stripped from llms output]<style>html[data-theme="dark"] div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}@media (prefers-color-scheme: dark){html:not([data-theme="light"]) div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}}</style><script>if (!window.plotlySphinxGalleryResize) {window.plotlySphinxGalleryResize = true;window.addEventListener("load", function () {document.querySelectorAll(".plotly-graph-div").forEach(function (gd) { Plotly.Plots.resize(gd); });});}</script>

<br/>

Let’s plot the portfolios’ composition:

```Python
population.plot_composition()
```

<style>html[data-theme="dark"] div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}@media (prefers-color-scheme: dark){html:not([data-theme="light"]) div.output_subarea:has(.plotly-graph-div){background:#fff;border-radius:0.25rem;padding:0.5rem}}</style><script>if (!window.plotlySphinxGalleryResize) {window.plotlySphinxGalleryResize = true;window.addEventListener("load", function () {document.querySelectorAll(".plotly-graph-div").forEach(function (gd) { Plotly.Plots.resize(gd); });});}</script>[plotly figure stripped from llms output]
<br />
<br />

**Total running time of the script:** (0 minutes 3.908 seconds)

<a id="sphx-glr-download-auto-examples-mean-risk-plot-13-factor-model-py"></a>
