<a id="skfolio-prior-characteristicsfactormodel"></a>

# skfolio.prior.CharacteristicsFactorModel

<a id="skfolio.prior.CharacteristicsFactorModel"></a>

### *class* skfolio.prior.CharacteristicsFactorModel(\*, factors, currency_factor=None, exposure_lag=1, cs_regressor=None, neutralize_against=None, constrained_families=None, benchmark_mcap_power=1.0, regression_mcap_power=0.5, inv_idio_variance_weight_shrinkage=0.0, inv_idio_variance_max_weight_ratio=20.0, factor_prior_estimator=None, alpha_estimator=None, spanned_alpha_shrinkage=1.0, orthogonal_alpha_confidence=1.0, idio_variance_estimator=None, idio_corr_estimator=None, idio_corr_threshold=0.0, max_history=None, min_regression_assets=None, n_jobs=1)

Characteristics-based cross-sectional factor model.

`CharacteristicsFactorModel` estimates a point-in-time, cross-sectional equity
factor model from asset characteristics stored in an
[`AssetPanel`](https://skfolio.org/generated/skfolio.containers.AssetPanel.html.md#skfolio.containers.AssetPanel) [[1]](#r3935960e9387-1).

The model is fitted as follows:

1. Start from point-in-time asset characteristics stored as panel fields (e.g.,
   `returns`, `market_cap`, `book_equity`, `industry`, `country`).
2. Compute descriptor values from these fields, or pass through existing fields
   unchanged, using descriptor estimators (e.g.
   [`BookToPrice`](https://skfolio.org/generated/skfolio.descriptor.BookToPrice.html.md#skfolio.descriptor.BookToPrice),
   [`EWMomentum`](https://skfolio.org/generated/skfolio.descriptor.EWMomentum.html.md#skfolio.descriptor.EWMomentum),
   [`Passthrough`](https://skfolio.org/generated/skfolio.descriptor.Passthrough.html.md#skfolio.descriptor.Passthrough)).
3. Build factor exposures. Style factors are typically formed by combining one or
   more descriptors and applying cross-sectional transformation such as
   winsorization and z-scoring, for example with
   [`FixedWeightedFactor`](https://skfolio.org/generated/skfolio.factor_exposure.FixedWeightedFactor.html.md#skfolio.factor_exposure.FixedWeightedFactor). Categorical
   factors (e.g. industry, country, currency) are represented by one-hot exposures
   with [`OneHotCategoricalFactors`](https://skfolio.org/generated/skfolio.factor_exposure.OneHotCategoricalFactors.html.md#skfolio.factor_exposure.OneHotCategoricalFactors).
4. Orthogonalize selected exposures against other factors or families when
   `neutralize_against` is provided.
5. Reparameterize constrained families when `constrained_families` is
   provided. This enforces the benchmark-weighted zero-sum constraint on factor
   returns within each constrained family and produces a full-rank basis for
   factor-level estimators, such as the factor covariance estimator.
6. Lag exposures by `exposure_lag` periods and estimate realized factor returns
   with `cs_regressor` on the estimation universe defined by the panel’s
   `estimation_mask`. By default, regression weights are based on market
   capitalization through `regression_mcap_power`. When
   `inv_idio_variance_weight_shrinkage > 0`, a two-pass procedure blends those
   weights with inverse-idiosyncratic-variance weights estimated from first-pass
   residuals.
7. Estimate the factor return distribution with `factor_prior_estimator`,
   including expected factor returns (factor premia), factor covariance, and factor
   return scenarios. This step can introduce factor covariance shrinkage,
   short-term volatility updating, or Newey-West HAC correction.
8. Estimate idiosyncratic variances with `idio_variance_estimator`, then form
   the idiosyncratic covariance as a diagonal matrix or, when
   `idio_corr_threshold > 0`, as a sparse covariance using correlation thresholding.
9. If provided, fit `alpha_estimator` to produce an alpha forecast. Decompose it
   into factor-spanned and orthogonal alphas, blend factor-implied asset expected
   returns with the spanned alpha using `spanned_alpha_shrinkage`, shrink the
   orthogonal alpha with `orthogonal_alpha_confidence` and assemble the final
   $\mu$, $\Sigma$ and asset return scenarios on the investment
   universe.

For asset $i$ and observation $t$, factor returns are estimated from the
cross-sectional regression

$$
R_i(t) = B_i(t - \ell)\,f(t) + \epsilon_i(t)
$$

where $R_i(t)$ is the local excess return, $B_i(t-\ell)$ denotes
asset $i$’s factor exposure vector, $f(t)$ is the vector of realized
factor returns, $\ell$ is the exposure lag and $\epsilon_i(t)$ is the
idiosyncratic return.

At each observation, this regression estimates realized factor returns and
idiosyncratic returns [[2]](#r3935960e9387-2). Expected asset returns are subsequently constructed from
expected factor returns and, when configured, an alpha forecast.

The estimator follows skfolio’s as-of time-indexing convention: all time-varying
inputs at observation $t$ reflect information available up to and including
the end of period $t$. Point-in-time fields and derived values store the
latest available value for observation $t$. Returns stored at observation
$t$ cover the period ending at $t$, namely $(t-1, t]$.

Factor-return regressions estimate the factor returns realized over
$(t-1, t]$. The exposure matrix must therefore describe the assets before
that return interval begins. `exposure_lag` selects that exposure date.

The fitted covariance uses the latest available exposures $B(T)$:

$$
\Sigma = B(T)\,F\,B(T)^\top + D
$$

where $F$ is the factor return covariance and $D$ is the idiosyncratic
covariance.

With the default settings and no alpha estimator, the fitted expected-return vector
is $\mu = B(T)\,\mu_f$, where $\mu_f$ contains expected factor returns.
When an alpha estimator is provided, its forecast is decomposed as

$$
\alpha = \alpha^{\parallel} + \alpha^{\perp},
\qquad
\alpha^{\parallel} = B(T)\,g
$$

where $\alpha^{\parallel}$ is the spanned alpha and
$\alpha^{\perp}$ is the orthogonal alpha. Expected returns are assembled as

$$
\mu
= \lambda\,B(T)\,\mu_f
+ (1 - \lambda)\,\alpha^{\parallel}
+ c\,\alpha^{\perp},
$$

where $\lambda$ is `spanned_alpha_shrinkage` and $c$ is
`orthogonal_alpha_confidence`. Direct currency expected returns are added when
currency factors are present.

Asset return scenarios in `return_distribution_.returns` are built by mapping
factor-prior scenarios through the latest loading matrix and adding idiosyncratic
scenarios calibrated to the latest idiosyncratic-risk forecast. They therefore
include both factor and idiosyncratic return components for downstream optimizers
that use scenario-based risk measures such as CVaR.

The estimator distinguishes the coverage, estimation and investment universes:

* The coverage universe is the set of assets stored in `characteristics`. The
  panel’s `active_mask` identifies which asset-observation pairs are active
  within that universe. If `active_mask=True` and a value is NaN, the observation
  is treated as missing data (e.g., holiday or missing quote). If
  `active_mask=False`, the asset is inactive at that observation (e.g.,
  pre-listing or post-delisting period). `AssetPanel` applies each field’s
  `inactive_policy` outside `active_mask`, commonly NaN for numeric fields and
  `MISSING=-1` for categorical fields.
* The estimation universe is defined by the panel’s `estimation_mask`, which is
  enforced as a subset of `active_mask`. It selects the active pairs used to fit
  cross-sectional statistics, factor-return regressions, benchmark and regression
  weights, alpha estimators and regime statistics. Other active pairs can still
  receive transformed values, exposures and forecasts, but they do not contribute to
  those fitted statistics.
* The investment universe is defined by `X.columns` when `X` is provided. `X` is
  the skfolio API input for asset returns and is used by downstream workflows for
  validation, cross-validation, prediction, and scoring. If `X` is `None`, the
  investment universe is the full coverage universe.

Fitting is performed on the coverage universe to use all available cross-sectional
information, then the outputs are reduced to the investment universe. Within that
investment universe, NaNs in fitted moments mark assets that are currently
unavailable or not yet warmed up. Compatible downstream optimizers infer the
investable subset from finite moments, solve on that subset and expand weights back
with zero weight for unavailable assets.

For the complete factor-model user guide, see
[Factor Models](https://skfolio.org/user_guide/factor_models.html.md#factor-models). For more details on the input panel format,
see [Asset Data Representation](https://skfolio.org/user_guide/data_representation.html.md#asset-data-representation).

* **Parameters:**
  **factors** *list of (str, BaseFactorExposure) tuples*
  : Named factor exposure estimators. Each tuple `(name, estimator)` defines a
    factor whose exposure is computed from the [`AssetPanel`](https://skfolio.org/generated/skfolio.containers.AssetPanel.html.md#skfolio.containers.AssetPanel).
    Every estimator must have a `family` attribute (e.g., `"market"`, `"style"`,
    `"industry"`, `"country"`). Factor families are used for neutralization,
    zero-sum constraints and reporting.

  **currency_factor** *BaseFactorExposure, optional*
  : Optional factor exposure estimator for a multi-currency universe. This is
    expected to be a `OneHotCategoricalFactors` estimator on the point-in-time asset
    currency field, or an equivalent estimator returning one-hot currency exposures.
    <br/>
    For asset $i$ with primary currency $C_i(t)$ at observation
    $t$, the exposure to currency factor $c$ is:
    $$
    x^{ccy}_{i,c}(t) =
    \begin{cases}
        1, & C_i(t) = c, \\
        0, & C_i(t) \ne c.
    \end{cases}
    $$
    <br/>
    Currency factor returns are supplied through `currency_excess_returns`. The
    base-currency excess return is:
    $$
    R^{excess,base}_i(t)
    = R^{excess,local}_i(t) + R^{ccy}_{C_i(t)}(t)
    $$
    <br/>
    where:
    $$
    R^{ccy}_{C_i(t)}(t)
    = R^{FX}_{C_i(t)}(t) + r^{cash}_{C_i(t)}(t)
    - r^{cash}_{base}(t) + R^{local}_i(t) R^{FX}_{C_i(t)}(t).
    $$
    <br/>
    Non-currency factor returns are estimated from local excess returns by
    cross-sectional regression. Currency factor returns are not estimated by that
    regression because they are observed FX series in the investor’s numeraire.
    They are appended directly to the factor return distribution with family
    `"currency"`. The family name `"currency"` is reserved for this estimator.

  **exposure_lag** *int, default=1*
  : Number of periods by which factor exposures are lagged in the cross-sectional
    regression. Must be >= 1.
    <br/>
    The estimator follows skfolio’s as-of time-indexing convention: all
    time-varying inputs at observation $t$ reflect information available up
    to and including the end of period $t$. Point-in-time fields and derived
    values store the latest available value for observation $t$. Returns
    stored at observation $t$ cover the period ending at $t$, namely
    $(t-1, t]$.
    <br/>
    Factor-return regressions estimate the factor returns realized over
    $(t-1, t]$. The exposure matrix must therefore describe the assets before
    that return interval begins. `exposure_lag` selects that exposure date. The
    regression model is:
    $$
    R(t) = B(t - \ell)\,f(t) + \epsilon(t)
    $$
    <br/>
    where $\ell$ is `exposure_lag`. With the default $\ell = 1$,
    returns over $(t-1, t]$ are regressed on exposures measured at
    $t-1$.

  **cs_regressor** *BaseCSLinearModel, optional*
  : Cross-sectional regression estimator used to estimate factor returns from asset
    returns and lagged exposures. Must have `fit_intercept=False`. To model an
    cross-sectional intercept, include a
    [`GlobalFactor`](https://skfolio.org/generated/skfolio.factor_exposure.GlobalFactor.html.md#skfolio.factor_exposure.GlobalFactor) in `factors`. The default
    (`None`) is to use
    [`CSLinearRegression`](https://skfolio.org/generated/skfolio.linear_model.CSLinearRegression.html.md#skfolio.linear_model.CSLinearRegression).
    <br/>
    #### NOTE
    Unlike factor exposures, which are typically winsorized and standardized
    by the exposure estimators, asset returns enter the cross-sectional
    regression unadjusted. Cleaning return data errors is an upstream
    responsibility, since the same returns drive benchmark weights, realized
    performance and downstream optimization. Winsorizing legitimate extreme
    returns would break the reconciliation of
    $R = B\,f + \epsilon$ and understate idiosyncratic risk for
    heavy-tailed assets; outlier influence is instead limited through
    exposure winsorization, `regression_mcap_power` and
    `inv_idio_variance_weight_shrinkage`. For bounded-influence estimation
    of factor returns, supply a robust `cs_regressor`.

  **neutralize_against** *dict of {str: list[str]}, optional*
  : Keys are factor names or family names to neutralize and values are lists of
    factor names or family names to neutralize against. When a key is a family name,
    every factor in that family is neutralized independently against the same
    targets. Entries are processed in insertion order: later entries see exposures
    already modified by earlier ones.
    <br/>
    For example:
    * `{"volatility": ["beta"]}`: orthogonalizes the volatility factor exposure
      against the beta factor exposure.
    * `{"momentum": ["industry"]}`: orthogonalizes the momentum factor exposure
      against all industry factor exposures.
    * `{"style": ["industry"]}`: orthogonalizes every style factor exposure against
      all industry factor exposures.
    <br/>
    #### NOTE
    When industry factors are one-hot encoded, neutralizing against industry is
    equivalent to applying within-industry demeaning. Using
    `FixedWeightedFactor` with `transform_by_group="industry"` and
    cross-sectional scoring that support group demeaning (e.g.
    `CSStandardScaler`) achieves the same result and is preferred for
    performance.

  **constrained_families** *list[tuple[str, str | None]], optional*
  : Zero-sum constraints applied within factor families. This is useful for one-hot
    families such as industry or country, whose exposures are collinear with
    [`GlobalFactor`](https://skfolio.org/generated/skfolio.factor_exposure.GlobalFactor.html.md#skfolio.factor_exposure.GlobalFactor) when all categories are
    included.
    <br/>
    Economically, the market factor captures the benchmark portfolio return.
    Constrained family factors capture relative effects around it. For example,
    constrained industry factors measure industry effects whose benchmark-weighted
    average is zero.
    <br/>
    Each tuple `(family, factor_to_drop)` specifies a family to reparameterize.
    The model removes one redundant factor and rewrites the family exposures in
    an equivalent full-rank basis. If `factor_to_drop` is `None`, the redundant
    factor is selected automatically to improve numerical conditioning.
    <br/>
    The same full-rank basis is used by downstream factor-level estimators, such
    as the factor prior and covariance estimator.

  **benchmark_mcap_power** *float, default=1.0*
  : Exponent applied to market capitalization to define the model benchmark weights:
    $$
    w_i \propto \mathrm{mcap}_i^p
    $$
    <br/>
    where $p$ is `benchmark_mcap_power`. These weights are used for weighted
    cross-sectional centering, zero-sum family constraints and the reference
    portfolio associated with the global factor.
    <br/>
    Typical choices:
    * `0.0`: equal-weighted
    * `0.5`: square-root market-cap-weighted
    * `1.0`: market-cap-weighted (default)

  **regression_mcap_power** *float, default=0.5*
  : Exponent applied to market capitalization to define the initial weights used in
    the cross-sectional regression:
    $$
    w_i \propto \mathrm{mcap}_i^p
    $$
    <br/>
    where $p$ is `regression_mcap_power`. These weights are used for
    regression estimation only and do not affect the model benchmark weights.
    When `inv_idio_variance_weight_shrinkage > 0`, these market-cap-based weights
    are blended with inverse-idiosyncratic-variance weights before the second-pass
    cross-sectional regression.
    <br/>
    Like the exposures, market caps are lagged by `exposure_lag`. Regression
    weights at observation $t$ use market caps from the selected exposure
    date, so the weights are fixed before the return interval being regressed.
    <br/>
    Typical choices:
    * `0.0`: equal-weighted
    * `0.5`: square-root market-cap-weighted (default)
    * `1.0`: market-cap-weighted

  **inv_idio_variance_weight_shrinkage** *float, default=0.0*
  : Shrinkage toward inverse-idiosyncratic-variance regression weights.
    When nonzero, the initial market-cap-based regression weights are blended with
    inverse idiosyncratic-variance weights in a two-pass WLS procedure. This
    approximates GLS by using estimated idiosyncratic variances as regression
    weights.
    <br/>
    The blended regression weight for each asset is:
    $$
    w_i = \lambda\,w_i^{\text{inv-var}} + (1 - \lambda)\,w_i^{\text{cap}}
    $$
    <br/>
    where $\lambda$ is `inv_idio_variance_weight_shrinkage`. Larger values
    give more weight to inverse idiosyncratic variance, while `0.0` uses only the
    market-cap-based regression weights. Must satisfy
    `0 <= inv_idio_variance_weight_shrinkage <= 1`.
    <br/>
    This is a standard two-step feasible GLS: the variances feeding the weights
    are estimated from the cap-weighted first-pass residuals, so the regression
    weights never depend on their own output, avoiding the feedback loop of
    recursive weighting schemes where a low estimated variance increases an
    asset’s weight and, in turn, its influence on later residuals. The weights
    used at date $t$ are estimated from residuals up to $t - 1$ only.

  **inv_idio_variance_max_weight_ratio** *float, default=20*
  : Maximum ratio between any inverse-idiosyncratic-variance weight and the
    cross-sectional median inverse-idiosyncratic-variance weight. This caps extreme
    regression weights for assets with very low estimated idiosyncratic variance.

  **factor_prior_estimator** *BasePrior, optional*
  : Prior estimator for the factor return distribution: expected returns (factor
    premia), covariance and factor return scenarios. It is fitted on the estimated
    factor return time series.
    <br/>
    The default (`None`) is to use [`EmpiricalPrior`](https://skfolio.org/generated/skfolio.prior.EmpiricalPrior.html.md#skfolio.prior.EmpiricalPrior) with
    [`EWMu`](https://skfolio.org/generated/skfolio.moments.EWMu.html.md#skfolio.moments.EWMu) and
    [`RegimeAdjustedEWCovariance`](https://skfolio.org/generated/skfolio.moments.RegimeAdjustedEWCovariance.html.md#skfolio.moments.RegimeAdjustedEWCovariance).

  **alpha_estimator** *BaseAlpha, optional*
  : Estimator producing an expected-return forecast for each asset from
    idiosyncratic returns and alpha signals computed from `AssetPanel` fields.
    The forecast is subsequently decomposed into spanned alpha and orthogonal
    alpha.
    <br/>
    If `None` (default), expected asset returns are determined entirely by the
    expected factor returns estimated by `factor_prior_estimator` (factor premia).
    <br/>
    The alpha should be expressed in expected-return units when it is combined with
    expected factor returns or used by optimizers that trade it off against
    realized-return quantities (e.g., transaction costs, market impact, turnover
    constraints or return targets). Unitless cross-sectional scores are appropriate
    only when the downstream objective treats them purely as ordinal signals.

  **spanned_alpha_shrinkage** *float, default=1.0*
  : Shrinkage applied to spanned alpha. The alpha forecast is decomposed as
    $\alpha = \alpha^{\parallel} + \alpha^{\perp}$, with
    $\alpha^{\parallel} = B(T)\,g$. The factor-implied asset expected returns
    $B(T)\,\mu_f$ are blended with the spanned alpha:
    $$
    \mu^{\parallel}
    = \lambda\,B(T)\,\mu_f + (1 - \lambda)\,\alpha^{\parallel}
    $$
    <br/>
    where $\mu_f$ contains expected factor returns and $\lambda$ is
    `spanned_alpha_shrinkage`.
    * `0`: use only the spanned alpha. When `alpha_estimator=None`, this sets the
      non-currency factor-spanned expected return to zero.
    * `1` (default): use only factor-implied asset expected returns.
    * Between 0 and 1: blend factor-implied asset expected returns with the
      spanned alpha.
    <br/>
    Must satisfy `0 <= spanned_alpha_shrinkage <= 1`.

  **orthogonal_alpha_confidence** *float, default=1.0*
  : Confidence weight applied to orthogonal alpha. After the alpha forecast is
    decomposed into $\alpha^{\parallel}$ and $\alpha^{\perp}$, the
    orthogonal alpha is shrunk toward zero:
    $$
    \mu = \mu^{\parallel} + c\,\alpha^{\perp}
    $$
    <br/>
    where $c$ is `orthogonal_alpha_confidence`.
    * `0`: discard the orthogonal alpha.
    * `1` (default): use the orthogonal alpha as-is.
    * Between 0 and 1: partially shrink the orthogonal alpha toward zero.
    <br/>
    Must satisfy `0 <= orthogonal_alpha_confidence <= 1`.
    <br/>
    As an alternative to shrinking the point estimate, orthogonal uncertainty can be
    handled at the optimizer level with
    [`OrthogonalMuUncertaintySet`](https://skfolio.org/generated/skfolio.uncertainty_set.OrthogonalMuUncertaintySet.html.md#skfolio.uncertainty_set.OrthogonalMuUncertaintySet) or
    [`OrthogonalCovarianceUncertaintySet`](https://skfolio.org/generated/skfolio.uncertainty_set.OrthogonalCovarianceUncertaintySet.html.md#skfolio.uncertainty_set.OrthogonalCovarianceUncertaintySet).

  **idio_variance_estimator** *BaseVariance, optional*
  : Variance estimator for idiosyncratic returns. It must support `partial_fit` so
    the model can recover per-asset variance estimates at each observation. These
    estimates are stored in `factor_model_.idio_variances`. The default (`None`) is
    [`RegimeAdjustedEWVariance`](https://skfolio.org/generated/skfolio.moments.RegimeAdjustedEWVariance.html.md#skfolio.moments.RegimeAdjustedEWVariance).

  **idio_corr_estimator** *BaseCovariance, optional*
  : Estimator for idiosyncratic correlation thresholding. Although this parameter
    accepts a [`BaseCovariance`](https://skfolio.org/generated/skfolio.moments.BaseCovariance.html.md#skfolio.moments.BaseCovariance) estimator, only the
    correlation component of its output is retained.
    <br/>
    The estimator is fitted on idiosyncratic returns standardized by their
    contemporaneous idiosyncratic volatility from `idio_variance_estimator`. The
    resulting covariance matrix is converted to a correlation matrix, thresholded
    with `idio_corr_threshold` and recombined with the latest per-asset
    idiosyncratic variances to produce the final idiosyncratic covariance.
    <br/>
    By construction, idiosyncratic returns should be nearly uncorrelated after
    removing the factor structure, so the idiosyncratic covariance is diagonal by
    default. Correlation thresholding addresses cases where this assumption can
    break down, such as linked securities, multiple share classes, ADRs versus
    ordinary shares, or dual listings. Without it, optimizers may treat highly
    related securities as diversified sources of idiosyncratic risk.
    <br/>
    Variances and correlations are estimated separately because a single full
    covariance estimator would mix per-asset variance estimation with off-diagonal
    correlation noise. This keeps per-asset variances driven by
    `idio_variance_estimator` and applies correlation thresholding only where
    residual correlations are large enough to retain.
    <br/>
    The default (`None`) is [`EWCovariance`](https://skfolio.org/generated/skfolio.moments.EWCovariance.html.md#skfolio.moments.EWCovariance) [[3]](#r3935960e9387-3).
    Correlation thresholding is used only when `idio_corr_threshold > 0`.

  **idio_corr_threshold** *float, default=0.0*
  : Absolute correlation threshold $\tau$ used for idiosyncratic correlation
    thresholding. Off-diagonal correlations with $|\rho_{ij}| \le \tau$ are
    set to zero. If `0` (default), correlation thresholding is disabled and the
    idiosyncratic covariance is diagonal.

  **max_history** *int, optional*
  : Maximum number of fitted observations to retain in time-series outputs and asset
    return scenarios. This applies to `return_distribution_.returns` and to fitted
    `factor_model_` histories such as `factor_returns`, `idio_returns`,
    `idio_variances`, `exposures`, and `regression_weights`.
    <br/>
    In incremental learning, setting `max_history` limits memory usage and keeps
    optimization with scenario-based risk measures, such as CVaR, computed on a
    rolling window of recent scenarios.
    * If `None` (default), all fitted observations are retained.
    * If an integer, only the last `max_history` fitted observations are retained.

  **min_regression_assets** *int, optional*
  : Minimum number of assets that must have all factor exposures finite and belong
    to the estimation universe at every post-warmup observation. If any observation
    falls below this threshold, a ValueError is raised. If `None` (default), the
    threshold is set automatically to `max(2 * n_factors, 30)` to reduce the risk
    of underspecified regressions and unstable factor-return estimates.

  **n_jobs** *int, default=1*
  : Number of parallel jobs used to compute factor exposures. Factors in the same
    dependency layer are computed in parallel using threads, avoiding copies of the
    underlying `AssetPanel`. Set to `-1` to use all available processors.
* **Attributes:**
  **return_distribution_** *ReturnDistribution*
  : Fitted [`ReturnDistribution`](https://skfolio.org/generated/skfolio.prior.ReturnDistribution.html.md#skfolio.prior.ReturnDistribution) containing the expected
    asset returns, covariance matrix, asset return scenarios and reference to
    the fitted [`FactorModel`](https://skfolio.org/generated/skfolio.prior.FactorModel.html.md#skfolio.prior.FactorModel).

  **factor_model_** *FactorModel*
  : Fitted [`FactorModel`](https://skfolio.org/generated/skfolio.prior.FactorModel.html.md#skfolio.prior.FactorModel) containing factor exposures, factor
    returns, factor covariance, idiosyncratic returns, idiosyncratic variances and
    idiosyncratic covariance. Stored `exposures` follow the as-of time-indexing
    convention for each observation.

  **cs_regressor_** *BaseCSLinearModel*
  : Fitted cross-sectional regression estimator.

  **factor_prior_estimator_** *BasePrior*
  : Fitted factor prior estimator.

  **alpha_estimator_** *BaseAlpha or None*
  : Fitted alpha estimator or `None` if no alpha estimator was provided.

  **idio_variance_estimator_** *BaseVariance*
  : Fitted idiosyncratic variance estimator.

  **idio_corr_estimator_** *BaseCovariance*
  : Fitted idiosyncratic correlation estimator.

  **n_assets_** *int*
  : Number of assets seen during fitting.

  **asset_names_** *ndarray of shape (n_assets,)*
  : Asset names in the coverage universe.

  **n_features_in_** *int*
  : Number of assets in the investment universe.

  **feature_names_in_** *ndarray of shape (n_features_in,)*
  : Asset names in the investment universe. When `X` is `None`, equals
    `asset_names_`.

### Methods

| [`fit`](#skfolio.prior.CharacteristicsFactorModel.fit)([X, y, currency_excess_returns])         | Fit the characteristics factor model.                                                    |
|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
| [`get_metadata_routing`](#skfolio.prior.CharacteristicsFactorModel.get_metadata_routing)()                       | Get metadata routing for this estimator.                                                 |
| [`get_params`](#skfolio.prior.CharacteristicsFactorModel.get_params)([deep])                           | Get the parameters of this estimator.                                                    |
| [`partial_fit`](#skfolio.prior.CharacteristicsFactorModel.partial_fit)([X, y, currency_excess_returns]) | Incrementally fit the characteristics factor model.                                      |
| [`set_fit_request`](#skfolio.prior.CharacteristicsFactorModel.set_fit_request)(\*[, characteristics, ...])  | Configure whether metadata should be requested to be passed to the `fit` method.         |
| [`set_params`](#skfolio.prior.CharacteristicsFactorModel.set_params)(\*\*params)                       | Set the parameters of this estimator.                                                    |
| [`set_partial_fit_request`](#skfolio.prior.CharacteristicsFactorModel.set_partial_fit_request)(\*[, ...])           | Configure whether metadata should be requested to be passed to the `partial_fit` method. |

### Notes

When the factor list includes a
[`GlobalFactor`](https://skfolio.org/generated/skfolio.factor_exposure.GlobalFactor.html.md#skfolio.factor_exposure.GlobalFactor), that factor acts as
the cross-sectional regression intercept because its exposure is one for every
asset. Its estimated return is close to the return of the market (defined as the
benchmark-weighted portfolio on the estimation universe).

When remaining factor exposures are centered so their benchmark-weighted average is
zero, as produced by [`CSStandardScaler`](https://skfolio.org/generated/skfolio.preprocessing.CSStandardScaler.html.md#skfolio.preprocessing.CSStandardScaler) and family
constraints, they satisfy:

$$
\sum_i w_i^{\text{bench}}\,B_{ij} = 0 \quad \forall\; j \neq 0
$$

This centers these factors around the market, so the global factor captures the
market return. If regression weights differ from benchmark weights, small tilts
can remain:

$$
\sum_i w_i^{\text{reg}} B_{ij}
$$

These tilts explain why the global factor return may differ slightly from the
exact market return.

When `regression_mcap_power == benchmark_mcap_power` and
`inv_idio_variance_weight_shrinkage == 0`, regression weights are proportional to
benchmark weights, the tilts vanish and the identity becomes exact:

$$
\hat{f}_0(t) = \sum_i \hat{w}_i^{\text{bench}} R_i(t)
$$

### References

* <a id='r3935960e9387-1'>**[1]**</a> “The Elements of Quantitative Investing”, Wiley Finance, Giuseppe A. Paleologo (2025).
* <a id='r3935960e9387-2'>**[2]**</a> “Active Portfolio Management: A Quantitative Approach for Producing Superior Returns and Controlling Risk”, McGraw-Hill, Grinold & Kahn (1999).
* <a id='r3935960e9387-3'>**[3]**</a> “Multivariate Exponentially Weighted Moving Covariance Matrix”, Technometrics, Hawkins & Maboudou-Tchao (2008).

### Examples

Build a characteristics factor model from market, industry and style exposures:

```pycon
>>> from skfolio.datasets import make_synthetic_characteristics
>>> from skfolio.descriptor import (
...     BookToPrice,
...     EWMomentum,
...     EWMarketBeta,
...     ForwardEarningsToPrice,
...     LogMarketCap,
... )
>>> from skfolio.factor_exposure import (
...     DerivedFactor,
...     FixedWeightedFactor,
...     GlobalFactor,
...     OneHotCategoricalFactors,
... )
>>> from skfolio.moments import EWMu, RegimeAdjustedEWCovariance
>>> from skfolio.prior import CharacteristicsFactorModel, EmpiricalPrior
>>>
>>> characteristics = make_synthetic_characteristics(
...     n_assets=100, n_observations=504, n_industries=5, random_state=0
... )
>>>
>>> # Market and industry factors.
>>> market_factor = GlobalFactor()
>>> industry_factors = OneHotCategoricalFactors(
...     category="industry",
...     family="industry",
... )
>>>
>>> # Style factors built from descriptors.
>>> beta_factor = FixedWeightedFactor(
...     descriptors=[("market_beta", EWMarketBeta(half_life=63))],
...     family="style",
...     transform_by_group="industry",
... )
>>> momentum_factor = FixedWeightedFactor(
...     descriptors=[("momentum", EWMomentum(half_life=126, skip=21))],
...     family="style",
...     transform_by_group="industry",
... )
>>> size_factor = FixedWeightedFactor(
...     descriptors=[("log_market_cap", LogMarketCap())],
...     family="style",
...     transform_by_group="industry",
... )
>>> earnings_yield_factor = FixedWeightedFactor(
...     descriptors=[
...         ("book_to_price", BookToPrice()),
...         ("forward_earnings_to_price", ForwardEarningsToPrice()),
...     ],
...     weights=[0.5, 0.5],
...     family="style",
...     transform_by_group="industry",
... )
>>>
>>> # Style exposure derived from an existing factor.
>>> non_linear_size_factor = DerivedFactor(
...     source="size",
...     func=lambda x: x**3,
...     transform_by_group="industry",
... )
>>>
>>> model = CharacteristicsFactorModel(
...     factors=[
...         ("market", market_factor),
...         ("industry", industry_factors),
...         ("beta", beta_factor),
...         ("momentum", momentum_factor),
...         ("size", size_factor),
...         ("earnings_yield", earnings_yield_factor),
...         ("non_linear_size", non_linear_size_factor),
...     ],
...     neutralize_against={
...         "momentum": ["industry"],
...         "non_linear_size": ["size"],
...     },
...     constrained_families=[("industry", None)],
...     factor_prior_estimator=EmpiricalPrior(
...         mu_estimator=EWMu(),
...         covariance_estimator=RegimeAdjustedEWCovariance(
...             half_life=40,
...             corr_half_life=60,
...         ),
...     ),
...     inv_idio_variance_weight_shrinkage=0.5,
...     n_jobs=-1,
... )
>>> model.fit(characteristics=characteristics)
CharacteristicsFactorModel(...)
>>>
>>> # Inspect the fitted factor model and diagnostics.
>>> fm = model.factor_model_
>>> fm.summary().head(3)
             annualized_mean  annualized_vol  ...  coverage  stability
market              0.250...        0.188...  ...       1.0        1.0
Real Estate        -0.000...        0.083...  ...       1.0        1.0
Software           -0.107...        0.118...  ...       1.0        1.0

[3 rows x 9 columns]
>>> fm.idio_calibration_summary()
mean_cs_std                0.997...
median_cs_std              0.988...
mean_cs_excess_kurtosis    0.116...
mean_cs_skewness           0.012...
mean_tail_rate_3sigma      0.003...
Name: idio_calibration, dtype: float64
>>> fm.idio_vol_ic.tail(3)
2016-12-02    0.418...
2016-12-05    0.496...
2016-12-06    0.488...
Name: Idio Vol IC (Spearman), dtype: float64
>>> fm.idio_tail_rate().tail(3)
2016-12-02    0.000000
2016-12-05    0.000000
2016-12-06    0.011...
Name: Tail Rate, dtype: float64
>>> fm.factor_returns_df.head(3)
              market  Real Estate  ...  earnings_yield  non_linear_size
2015-07-27 -0.001...     0.001...  ...        0.000...        -0.002...
2015-07-28  0.010...     0.000...  ...        0.003...         0.001...
2015-07-29 -0.006...     0.011...  ...       -0.000...         0.002...

[3 rows x 11 columns]
>>> # Last three size exposures for the first three assets.
>>> fm.exposures_df["size"].iloc[-3:, :3]
asset         A00000    A00001    A00002
2016-12-02 -2.314... -1.344... -0.283...
2016-12-05 -2.312... -1.339... -0.283...
2016-12-06 -2.313... -1.339... -0.287...
```

Use [`partial_fit`](#skfolio.prior.CharacteristicsFactorModel.partial_fit) for online updates:

```pycon
>>> model.fit(characteristics=characteristics[:400])
CharacteristicsFactorModel(...)
>>> model.partial_fit(characteristics=characteristics[400:405])
CharacteristicsFactorModel(...)
```

<a id="skfolio.prior.CharacteristicsFactorModel.fit"></a>

#### fit(X=None, y=None, \*, characteristics, currency_excess_returns=None, \*\*fit_params)

Fit the characteristics factor model.

Resets all fitted state and estimates the full model pipeline on the provided
data. For incremental updates, use [`partial_fit`](#skfolio.prior.CharacteristicsFactorModel.partial_fit).

* **Parameters:**
  **X** *DataFrame of shape (n_observations, n_assets), optional*
  : Asset returns whose columns define the investment universe, following the
    standard skfolio estimator API. In this estimator, `X.columns` define the
    assets returned in `return_distribution_` and `factor_model_`.
    <br/>
    Factor estimation uses the `"returns"` field of `characteristics`, which can
    cover a broader point-in-time universe than `X`. This keeps the estimator
    compatible with skfolio pipelines, cross-validation, prediction and scoring,
    while allowing the factor model to use a wider coverage universe for
    estimation.

  **y** *Ignored*
  : Not used, present for API consistency by convention.

  **characteristics** *AssetPanel*
  : Point-in-time [`AssetPanel`](https://skfolio.org/generated/skfolio.containers.AssetPanel.html.md#skfolio.containers.AssetPanel) for the coverage
    universe. Must include `"returns"` and, when market-cap weighting is used,
    `"market_cap"`. The panel’s `active_mask` identifies active
    asset-observation pairs within the coverage universe and `estimation_mask`
    identifies which active pairs contribute to estimation. For more details see
    [Asset Data Representation](https://skfolio.org/user_guide/data_representation.html.md#asset-data-representation).

  **currency_excess_returns** *DataFrame, optional*
  : Currency excess returns. Required only when `currency_factor` is set.
    Columns must contain the unique currency factor names produced by
    `currency_factor`. Assets are mapped to these columns through the one-hot
    currency exposures.

  **\*\*fit_params** *dict*
  : Parameters passed to underlying estimators. Only available when
    `enable_metadata_routing=True`, set with
    `sklearn.set_config(enable_metadata_routing=True)`. See
    [Metadata Routing User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) for more details.
* **Returns:**
  **self** *CharacteristicsFactorModel*
  : Fitted estimator.
* **Raises:**
  ValueError
  : If the data does not contain enough observations to estimate the model after
    warmup and exposure-lag trimming.

<a id="skfolio.prior.CharacteristicsFactorModel.get_metadata_routing"></a>

#### get_metadata_routing()

Get metadata routing for this estimator.

* **Returns:**
  **routing** *MetadataRouter*
  : Metadata routing configuration.

<a id="skfolio.prior.CharacteristicsFactorModel.get_params"></a>

#### get_params(deep=True)

Get the parameters of this estimator.

Returns the parameters given in the constructor as well as the
factor estimators contained within the `factors` parameter.

* **Parameters:**
  **deep** *bool, default=True*
  : Setting it to True gets the various estimators and the parameters
    of the estimators as well.
* **Returns:**
  **params** *dict*
  : Parameter and estimator names mapped to their values or parameter
    names mapped to their values.

<a id="skfolio.prior.CharacteristicsFactorModel.named_factors"></a>

#### *property* named_factors

Dictionary for accessing factors by name.

* **Returns:**
  `Bunch`

<a id="skfolio.prior.CharacteristicsFactorModel.partial_fit"></a>

#### partial_fit(X=None, y=None, \*, characteristics, currency_excess_returns=None, \*\*fit_params)

Incrementally fit the characteristics factor model.

This method allows for streaming/online updates. Each call updates the internal
state with new observations without resetting previously accumulated state. All
sub-estimators (`factor_prior_estimator`, `idio_variance_estimator`, etc.)
must implement `partial_fit` for this method to work.

* **Parameters:**
  **X** *DataFrame of shape (n_observations, n_assets), optional*
  : Asset returns whose columns define the investment universe, following the
    standard skfolio estimator API. In this estimator, `X.columns` define the
    assets returned in `return_distribution_` and `factor_model_`.
    <br/>
    Factor estimation uses the `"returns"` field of `characteristics`, which can
    cover a broader point-in-time universe than `X`. This keeps the estimator
    compatible with skfolio pipelines, cross-validation, prediction and scoring,
    while allowing the factor model to use a wider coverage universe for
    estimation.

  **y** *Ignored*
  : Not used, present for API consistency by convention.

  **characteristics** *AssetPanel*
  : Point-in-time [`AssetPanel`](https://skfolio.org/generated/skfolio.containers.AssetPanel.html.md#skfolio.containers.AssetPanel) for the coverage
    universe. Must include `"returns"` and, when market-cap weighting is used,
    `"market_cap"`. The panel’s `active_mask` identifies active
    asset-observation pairs within the coverage universe and `estimation_mask`
    identifies which active pairs contribute to estimation. For more details see
    [Asset Data Representation](https://skfolio.org/user_guide/data_representation.html.md#asset-data-representation).

  **currency_excess_returns** *DataFrame, optional*
  : Currency excess returns. Required only when `currency_factor` is set.
    Columns must contain the unique currency factor names produced by
    `currency_factor`. Assets are mapped to these columns through the one-hot
    currency exposures.

  **\*\*fit_params** *dict*
  : Parameters passed to underlying estimators. Only available when
    `enable_metadata_routing=True`, set with
    `sklearn.set_config(enable_metadata_routing=True)`. See
    [Metadata Routing User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) for more details.
* **Returns:**
  **self** *CharacteristicsFactorModel*
  : Fitted estimator.

<a id="skfolio.prior.CharacteristicsFactorModel.set_fit_request"></a>

#### set_fit_request(\*, characteristics='$UNCHANGED$', currency_excess_returns='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the `fit` method.

Note that this method is only relevant when this estimator is used as a
sub-estimator within a meta-estimator and metadata routing is enabled
with `enable_metadata_routing=True` (see `sklearn.set_config`).
Please check the [User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) on how the routing
mechanism works.

The options for each parameter are:

- `True`: metadata is requested, and passed to `fit` if provided. The request is ignored if metadata is not provided.
- `False`: metadata is not requested and the meta-estimator will not pass it to `fit`.
- `None`: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
- `str`: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (`sklearn.utils.metadata_routing.UNCHANGED`) retains the
existing request. This allows you to change the request for some
parameters and not others.

#### Versionadded
Added in version 1.3.

* **Parameters:**
  **characteristics** *str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED*
  : Metadata routing for `characteristics` parameter in `fit`.

  **currency_excess_returns** *str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED*
  : Metadata routing for `currency_excess_returns` parameter in `fit`.
* **Returns:**
  **self** *object*
  : The updated object.

<a id="skfolio.prior.CharacteristicsFactorModel.set_params"></a>

#### set_params(\*\*params)

Set the parameters of this estimator.

Valid parameter keys can be listed with `get_params()`. Note that you
can directly set the parameters of the factor estimators contained in
`factors`.

* **Parameters:**
  **\*\*params** *keyword arguments*
  : Specific parameters using e.g.
    `set_params(parameter_name=new_value)`. In addition to setting the
    parameters of the estimator, the individual factor estimators can
    also be set, or can be removed by setting them to ‘drop’.
* **Returns:**
  **self** *object*
  : Estimator instance.

<a id="skfolio.prior.CharacteristicsFactorModel.set_partial_fit_request"></a>

#### set_partial_fit_request(\*, characteristics='$UNCHANGED$', currency_excess_returns='$UNCHANGED$')

Configure whether metadata should be requested to be passed to the `partial_fit` method.

Note that this method is only relevant when this estimator is used as a
sub-estimator within a meta-estimator and metadata routing is enabled
with `enable_metadata_routing=True` (see `sklearn.set_config`).
Please check the [User Guide](https://skfolio.org/user_guide/metadata_routing.html.md#metadata-routing) on how the routing
mechanism works.

The options for each parameter are:

- `True`: metadata is requested, and passed to `partial_fit` if provided. The request is ignored if metadata is not provided.
- `False`: metadata is not requested and the meta-estimator will not pass it to `partial_fit`.
- `None`: metadata is not requested, and the meta-estimator will raise an error if the user provides it.
- `str`: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (`sklearn.utils.metadata_routing.UNCHANGED`) retains the
existing request. This allows you to change the request for some
parameters and not others.

#### Versionadded
Added in version 1.3.

* **Parameters:**
  **characteristics** *str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED*
  : Metadata routing for `characteristics` parameter in `partial_fit`.

  **currency_excess_returns** *str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED*
  : Metadata routing for `currency_excess_returns` parameter in `partial_fit`.
* **Returns:**
  **self** *object*
  : The updated object.

