#### NOTE
[Go to the end](#sphx-glr-download-auto-examples-entropy-pooling-plot-2-opinion-pooling-py)
to download the full example code or to run this example in your browser via JupyterLite.

<a id="sphx-glr-auto-examples-entropy-pooling-plot-2-opinion-pooling-py"></a>

<a id="opinion-pooling"></a>

# Opinion Pooling

This tutorial introduces the [`OpinionPooling`](https://skfolio.org/generated/skfolio.prior.OpinionPooling.html.md#skfolio.prior.OpinionPooling) estimator.

<a id="introduction"></a>

## Introduction

Opinion Pooling (also called Belief Aggregation or Risk Aggregation) is a process
in which different probability distributions (opinions), produced by different
experts, are combined to yield a single probability distribution (consensus).

Expert opinions (also called individual prior distributions) can be
**elicited** from domain experts or **derived** from quantitative analyses.

The `OpinionPooling` estimator takes a list of prior estimators, each of which
produces scenario probabilities (`sample_weight`), and pools them into a single
consensus probability .

You can choose between linear (arithmetic) pooling or logarithmic (geometric)
pooling, and optionally apply robust pooling using a Kullback-Leibler divergence
penalty to down-weight experts whose views deviate strongly from the group.

<a id="linear-opinion-pooling"></a>

### Linear Opinion Pooling

* Retains all nonzero support: no “zero-forcing”
* Produces an averaging that is more evenly spread across all expert opinions.

<a id="logarithmic-opinion-pooling"></a>

### Logarithmic Opinion Pooling

* Zero-Preservation: any scenario assigned zero probability by any expert
  remains zero in the aggregate.
* Information-Theoretic Optimality: yields the distribution that minimizes the
  weighted sum of KL-divergences from each expert’s distribution.
* Robust to Extremes: down-weight extreme or contrarian views more severely.

<a id="robust-pooling-with-divergence-penalty"></a>

### Robust Pooling with Divergence Penalty

By specifying a `divergence_penalty`, you can penalize each opinion’s
divergence from the group consensus, yielding a more robust aggregate distribution.

In this tutorial, we will:
: 1. Apply Opinion Pooling to historical return data.
  2. Construct portfolios based on the adjusted distribution.
  3. Demonstrate factor-based and synthetic-data-enhanced Opinion Pooling.
  4. Perform stress tests using Opinion Pooling.

<a id="data-loading-and-preparation"></a>

## Data Loading and Preparation

We load the S&P 500 [dataset](https://skfolio.org/user_guide/datasets.html.md#datasets) and select seven stocks
(for demonstration purposes). We also load the factors dataset, composed of
daily prices for five ETFs representing common factors.

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

from skfolio import Population, RiskMeasure
from skfolio.datasets import load_factors_dataset, load_sp500_dataset
from skfolio.distribution import VineCopula
from skfolio.measures import (
    cvar,
    kurtosis,
    mean,
    skew,
    standard_deviation,
    value_at_risk,
)
from skfolio.optimization import HierarchicalRiskParity, RiskBudgeting
from skfolio.preprocessing import prices_to_returns
from skfolio.prior import (
    EntropyPooling,
    OpinionPooling,
    SyntheticData,
    TimeSeriesFactorModel,
)
from skfolio.utils.figure import plot_kde_distributions

# Load stock price and factor data
prices = load_sp500_dataset()
prices = prices[["AMD", "BAC", "GE", "JNJ", "JPM", "LLY", "PG"]]
factor_prices = load_factors_dataset()

# Convert to daily returns
X, factors = prices_to_returns(prices, factor_prices)

print("Shapes:")
print(f"X: {X.shape}")
print(f"factors: {factors.shape}")

print(X.tail())
print(factors.tail())
```

```none
Shapes:
X: (2263, 7)
factors: (2263, 5)
                 AMD       BAC        GE  ...       JPM       LLY        PG
Date                                      ...
2022-12-21  0.040430  0.015223  0.033001  ...  0.011248  0.023275  0.009170
2022-12-22 -0.056442 -0.008848 -0.014582  ... -0.011355 -0.007339  0.002308
2022-12-23  0.010335  0.002443  0.000235  ...  0.004749  0.007090  0.002825
2022-12-27 -0.019374  0.001875  0.012849  ...  0.003504 -0.008208  0.008713
2022-12-28 -0.011064  0.007360 -0.010502  ...  0.005463  0.000932 -0.012926

[5 rows x 7 columns]
                MTUM      QUAL      SIZE      USMV      VLUE
Date
2022-12-21  0.014312  0.017884  0.014371  0.012005  0.013246
2022-12-22 -0.010977 -0.015411 -0.012070 -0.007315 -0.011989
2022-12-23  0.010897  0.005889  0.006287  0.005281  0.005844
2022-12-27  0.001770 -0.003138 -0.001320  0.001798 -0.000111
2022-12-28 -0.011778 -0.013325 -0.013914 -0.010489 -0.015238
```

<a id="summary-statistics"></a>

### Summary Statistics

We create a helper function to compute key return statistics, optionally weighted by
sample probabilities:

```Python
def summary(X: pd.DataFrame, sample_weight: np.ndarray | None = None) -> pd.DataFrame:
    return pd.DataFrame(
        {
            "Mean": mean(X, sample_weight=sample_weight),
            "Volatility": standard_deviation(X, sample_weight=sample_weight),
            "Skew": skew(X, sample_weight=sample_weight),
            "Kurtosis": kurtosis(X, sample_weight=sample_weight),
            "VaR at 95%": value_at_risk(X, beta=0.95, sample_weight=sample_weight),
            "CVaR at 95%": cvar(X, beta=0.95, sample_weight=sample_weight),
        }
    )

summary(X)
```

[plotly figure stripped from llms output]
<br />
<br />

<a id="building-a-portfolio-based-on-opinion-pooling"></a>

## Building a Portfolio based on Opinion Pooling

Now that we’ve shown how the Opinion Pooling estimator works in isolation, let’s
see how to implement a risk parity portfolio with CVaR-90% as the risk measure based
on Opinion Pooling:

```Python
model = RiskBudgeting(
    risk_measure=RiskMeasure.CVAR, cvar_beta=0.9, prior_estimator=opinion_pooling
)

model.fit(X)

print(model.weights_)
```

```none
[0.08085596 0.0978985  0.0972087  0.21843437 0.10682442 0.17225667
 0.22652138]
```

<a id="factor-opinion-pooling"></a>

## Factor Opinion Pooling

Instead of applying Opinion Pooling directly to asset returns, we can embed it
within a factor model so that expert views are expressed on the factors.

```Python
factor_opinion_1 = EntropyPooling(
    mean_views=["QUAL == -0.0005"], cvar_views=["SIZE == 0.08"]
)
factor_opinion_2 = EntropyPooling(cvar_views=["SIZE == 0.09"])

factor_opinion_pooling = OpinionPooling(
    estimators=[("opinion_1", factor_opinion_1), ("opinion_2", factor_opinion_2)],
    opinion_probabilities=[0.6, 0.4],
)

factor_model = TimeSeriesFactorModel(factor_prior_estimator=factor_opinion_pooling)

model = RiskBudgeting(risk_measure=RiskMeasure.CVAR, prior_estimator=factor_model)

model.fit(X, factors=factors)
print(model.weights_)

sample_weight = model.prior_estimator_.return_distribution_.sample_weight
summary(factors, sample_weight)
```

```none
[0.09333215 0.09726882 0.10925525 0.21357395 0.10861813 0.17645262
 0.20149908]
```

[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="conclusion"></a>

## Conclusion

In this tutorial, we demonstrated how to leverage Opinion Pooling to aggregate
multiple expert views into every stage of portfolio management, from ex-ante
optimization to ex-post stress testing.

<a id="references"></a>

## References

[1] “Probabilistic opinion pooling generalized”,
: Social Choice and Welfare, Dietrich & List (2017)

[2] “Opinion Aggregation and Individual Expertise”,
: Oxford University Press, Martini & Sprenger (2017)

[3] “Rational Decisions”,
: Journal of the Royal Statistical Society, Good  (1952)

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

<a id="sphx-glr-download-auto-examples-entropy-pooling-plot-2-opinion-pooling-py"></a>
[![Launch JupyterLite](auto_examples/entropy_pooling/images/jupyterlite_badge_logo.svg)](../../lite/lab/index.html?path=auto_examples/entropy_pooling/plot_2_opinion_pooling.ipynb)

[`Download Jupyter notebook: plot_2_opinion_pooling.ipynb`](https://skfolio.org/auto_examples/entropy_pooling/_downloads/eaeaf1f05f2b1df8f128512515a18ba1/plot_2_opinion_pooling.ipynb)

[`Download Python source code: plot_2_opinion_pooling.py`](https://skfolio.org/auto_examples/entropy_pooling/_downloads/cbaa2f752def7a12e8c619b641e28484/plot_2_opinion_pooling.py)

[`Download zipped: plot_2_opinion_pooling.zip`](https://skfolio.org/auto_examples/entropy_pooling/_downloads/2e3666d66fffbaa4613cb1c1711ad7bc/plot_2_opinion_pooling.zip)

[Gallery generated by Sphinx-Gallery](https://sphinx-gallery.github.io)
