sf_quant.optimizer.dynamic_mve_optimizer#

sf_quant.optimizer.dynamic_mve_optimizer(ids: list[str], alphas: ndarray, factor_exposures: ndarray, factor_covariance: ndarray, specific_risk: ndarray, constraints: list[Constraint], initial_gamma: float = 100, betas: ndarray | None = None, target_active_risk: float | None = None, benchmark_weights: ndarray | None = None, active_weights: bool = False) DataFrame#

Mean-variance optimizer with optional active risk calibration.

Extends mve_optimizer() with the ability to automatically calibrate the risk aversion parameter (gamma) to achieve a target level of active risk (tracking error) relative to a benchmark portfolio. Uses iterative optimization with linear regression to solve for the gamma that produces the desired active risk.

Parameters#

idslist[str]

Asset identifiers (e.g., ticker symbols or BARRAs).

alphasnp.ndarray

Expected returns for each asset, shape (n_assets,).

factor_exposuresnp.ndarray

B matrix of shape (n_assets, n_factors), containing asset factor exposures.

factor_covariancenp.ndarray

F matrix of shape (n_factors, n_factors), factor covariance matrix.

specific_risknp.ndarray

D vector of shape (n_assets,), idiosyncratic variance per asset.

constraintslist[Constraint]

List of constraint objects implementing the Constraint protocol.

initial_gammafloat, optional

Starting value for gamma in calibration. Also used as the warm-start seed if target_active_risk is specified. Default is 100.

betasnp.ndarray, optional

Predicted betas or other asset-level values required by certain constraints.

target_active_riskfloat, optional

If specified, automatically calibrate gamma to achieve this target annualized active risk (e.g., 0.05 for 5%). Requires benchmark_weights. If not specified, uses initial_gamma directly.

benchmark_weightsnp.ndarray, optional

Benchmark portfolio weights of shape (n_assets,), required if target_active_risk is specified.

active_weightsbool

Flag indicating how to treat output weights of optimizer. False (default) means that we subtract of benchmark weights before computing active risk.

Returns#

pl.DataFrame

Polars DataFrame with columns:

  • barrid : str, asset identifier.

  • weight : float, optimized portfolio weight.

  • gamma : float, calibrated risk aversion parameter.

  • active_risk : float, achieved annualized active risk.

See Also#

mve_optimizer : Base mean-variance optimizer without active risk calibration. _calibrate_gamma : Gamma calibration routine.

Examples#

>>> import sf_quant.optimizer as sfo
>>> import numpy as np
>>> ids = ['AAPL', 'IBM']
>>> alphas = np.array([1.1, 1.2])
>>> factor_exposures = np.array([[0.8, 0.5], [1.2, 0.3]])
>>> factor_covariance = np.array([[0.5, 0.1], [0.1, 0.2]])
>>> specific_risk = np.array([0.1, 0.15])
>>> benchmark_weights = np.array([0.4, 0.6])
>>> constraints = [sfo.FullInvestment()]
>>> weights = sfo.dynamic_mve_optimizer(
...     ids=ids,
...     alphas=alphas,
...     factor_exposures=factor_exposures,
...     factor_covariance=factor_covariance,
...     specific_risk=specific_risk,
...     constraints=constraints,
...     initial_gamma=100,
...     target_active_risk=0.05,
...     benchmark_weights=benchmark_weights
... )
>>> weights
shape: (2, 4)
┌────────┬────────┬───────┬──────────────┐
│ barrid ┆ weight ┆ gamma ┆ active_risk  │
│ ---    ┆ ---    ┆ ---   ┆ ---          │
│ str    ┆ f64    ┆ f64   ┆ f64          │
╞════════╪════════╪═══════╪══════════════╡
│ AAPL   ┆ 0.40   ┆ 75.5  ┆ 0.05         │
│ IBM    ┆ 0.60   ┆ 75.5  ┆ 0.05         │
└────────┴────────┴───────┴──────────────┘