<a id="sphx-glr-auto-examples-online-learning-plot-3-online-portfolio-optimization-evaluation-py"></a>

<a id="online-evaluation-of-portfolio-optimization"></a>

# Online Evaluation of Portfolio Optimization

This tutorial shows how to tune a [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk) estimator with
online search and evaluate it out-of-sample with an online walk-forward procedure.

Unlike the [previous tutorial](https://skfolio.org/auto_examples/online_learning/plot_2_online_hyperparameter_tuning.html.md#sphx-glr-auto-examples-online-learning-plot-2-online-hyperparameter-tuning-py),
which tuned the covariance estimator in isolation, here we optimize the portfolio model
end-to-end using a portfolio-level metric.

The online approach is equivalent to combining scikit-learn’s
`GridSearchCV` with
[`WalkForward`](https://skfolio.org/generated/skfolio.model_selection.WalkForward.html.md#skfolio.model_selection.WalkForward) using `expand_train=True`, but instead of
refitting every candidate from scratch at each split, it calls `partial_fit` to
incrementally update each estimator. This is significantly faster for estimators that
support this method.

<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 S&P 500 Index composition starting from 2010-01-04 up to 2022-12-28.

```Python
import numpy as np
from plotly.io import show

from skfolio import Population
from skfolio.datasets import load_sp500_dataset
from skfolio.model_selection import OnlineGridSearch, online_predict
from skfolio.moments import EWMu, RegimeAdjustedEWCovariance
from skfolio.optimization import MeanRisk, ObjectiveFunction
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import EmpiricalPrior

prices = load_sp500_dataset()
X = prices_to_returns(prices)
X = X["2010":]
```

<a id="baseline-portfolio-model"></a>

## Baseline Portfolio Model

We start with a simple Minimum Variance optimization via
[`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk). All sub-estimators support `partial_fit`, so
the entire pipeline can be updated incrementally during walk-forward evaluation.

```Python
baseline_model = MeanRisk(
    prior_estimator=EmpiricalPrior(
        mu_estimator=EWMu(half_life=40),
        covariance_estimator=RegimeAdjustedEWCovariance(
            half_life=40,
            corr_half_life=80,
            regime_half_life=20,
        ),
    ),
)
```

<a id="online-portfolio-search"></a>

## Online Portfolio Search

We search directly over the portfolio estimator using a portfolio-level metric. For
portfolio optimization estimators, [`OnlineGridSearch`](https://skfolio.org/generated/skfolio.model_selection.OnlineGridSearch.html.md#skfolio.model_selection.OnlineGridSearch)
defaults to the Sharpe ratio when `scoring=None`.

We tune both the optimization objective and a few covariance hyperparameters.
The double-underscore syntax reaches into nested sub-estimators, just like in
scikit-learn model selection.

Here, `warmup_size=252` reserves the first year of observations for initialization and
`test_size=5` evaluates windows of 5 consecutive daily observations (one trading week).

```Python
portfolio_search = OnlineGridSearch(
    estimator=baseline_model,
    param_grid={
        "objective_function": [
            ObjectiveFunction.MINIMIZE_RISK,
            ObjectiveFunction.MAXIMIZE_RATIO,
        ],
        "prior_estimator__covariance_estimator__half_life": [20, 40, 60],
        "prior_estimator__covariance_estimator__corr_half_life": [40, 80],
    },
    warmup_size=252,
    test_size=5,
    n_jobs=-1,
    return_predictions=True,
)
portfolio_search.fit(X)
```

[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>

<a id="online-score"></a>

## Online Score

[`online_score`](https://skfolio.org/generated/skfolio.model_selection.online_score.html.md#skfolio.model_selection.online_score) runs the same walk-forward evaluation
and returns a scalar score. This is useful when the prediction path is not needed:

```python
from skfolio.measures import RatioMeasure
from skfolio.model_selection import online_score

baseline_score = online_score(
    baseline_model,
    X,
    warmup_size=252,
    test_size=5,
    scoring=RatioMeasure.ANNUALIZED_SHARPE_RATIO,
)
tuned_score = online_score(
    portfolio_search.best_estimator_,
    X,
    warmup_size=252,
    test_size=5,
    scoring=RatioMeasure.ANNUALIZED_SHARPE_RATIO,
)
```

Both prediction paths are already available here, so the scores are read from them
below rather than running two additional walk-forward passes:

```Python
baseline_score = baseline_prediction.annualized_sharpe_ratio
tuned_score = tuned_prediction.annualized_sharpe_ratio

print(f"Baseline Sharpe: {baseline_score:.4f}")
print(f"Tuned Sharpe: {tuned_score:.4f}")
```

```none
Baseline Sharpe: 0.9365
Tuned Sharpe: 0.9662
```

<a id="conclusion"></a>

## Conclusion

This tutorial demonstrated the portfolio-level online workflow:

1. Define an incremental [`MeanRisk`](https://skfolio.org/generated/skfolio.optimization.MeanRisk.html.md#skfolio.optimization.MeanRisk) estimator whose
   sub-estimators all support `partial_fit`.
2. Tune it with [`OnlineGridSearch`](https://skfolio.org/generated/skfolio.model_selection.OnlineGridSearch.html.md#skfolio.model_selection.OnlineGridSearch) using a
   portfolio-level metric.
3. Evaluate the tuned estimator out-of-sample with
   [`online_predict`](https://skfolio.org/generated/skfolio.model_selection.online_predict.html.md#skfolio.model_selection.online_predict) and visualize the results
   with [`Population`](https://skfolio.org/generated/skfolio.population.Population.html.md#skfolio.population.Population).
4. Summarize the walk-forward performance as a scalar with
   [`online_score`](https://skfolio.org/generated/skfolio.model_selection.online_score.html.md#skfolio.model_selection.online_score).

This complements the
[previous tutorial](https://skfolio.org/auto_examples/online_learning/plot_2_online_hyperparameter_tuning.html.md#sphx-glr-auto-examples-online-learning-plot-2-online-hyperparameter-tuning-py):
covariance tuning improves the statistical forecast, while direct portfolio search
optimizes the full allocation problem end-to-end.

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

<a id="sphx-glr-download-auto-examples-online-learning-plot-3-online-portfolio-optimization-evaluation-py"></a>
