Extract implied factor risk premia from portfolio weights in 5 minutes
BRISMA (Bantleon Risk Model Analysis) extracts implied factor risk premia from observed portfolio weights, then uses those premia to price new assets.
git clone https://github.com/Digital-AI-Finance/implied-risk-premia.git
cd implied-risk-premia
# Install UV (if not installed)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# Create virtual environment and install dependencies
uv sync
.venv\Scripts\Activate
python -c "import brisma; print(f'BRISMA v{brisma.__version__}')"
# Output: BRISMA v0.2.0
| Package | Purpose |
|---|---|
numpy, scipy | Numerical computing, optimization |
polars | Fast DataFrames |
arch | GARCH volatility models |
cvxpy | Convex optimization |
plotly | Interactive charts |
This minimal example shows the complete workflow: portfolio → implied returns → factor premia → pricing.
import numpy as np
from brisma import inverse_optimize_mv, extract_factor_premia_ols, price_new_asset
# 1. Define portfolio (Equity 40%, Bonds 35%, Credit 20%, Cash 5%)
weights = np.array([0.40, 0.35, 0.20, 0.05])
cov = np.array([
[0.0400, 0.0050, 0.0150, 0.0000], # Equity
[0.0050, 0.0100, 0.0040, 0.0000], # Bonds
[0.0150, 0.0040, 0.0225, 0.0000], # Credit
[0.0000, 0.0000, 0.0000, 0.0001], # Cash
])
# 2. Extract implied returns (inverse mean-variance optimization)
result = inverse_optimize_mv(weights, cov, lambda_=2.5)
print("Implied returns:", result.mu)
# [0.052, 0.025, 0.041, 0.001] - annualized
# 3. Extract factor premia (OLS regression on factor betas)
# Betas: [Duration, Credit] for each asset
betas = np.array([
[0.0, 0.8], # Equity: no duration, credit exposure
[7.0, 0.0], # Bonds: 7yr duration, no credit
[3.5, 1.0], # Credit: 3.5yr duration, credit exposure
[0.0, 0.0], # Cash: no factor exposure
])
premia = extract_factor_premia_ols(result.mu, betas, rf=0.02)
print(f"Duration premium: {premia.factor_premia[0]:.2%}/yr")
print(f"Credit premium: {premia.factor_premia[1]:.2%}/yr")
# 4. Price a new asset (High Yield fund: dur=4, credit=1.5)
new_beta = np.array([4.0, 1.5])
new_mu = price_new_asset(premia.factor_premia, new_beta, rf=0.02)
print(f"New asset expected return: {new_mu:.2%}")
For production use, the BRISMA class provides a clean interface:
from brisma import BRISMA
# Load data and run full pipeline
model = BRISMA.from_excel("data/brisma_data.xlsx")
result = model.run_pipeline()
# Access results
print(result.implied_returns)
print(result.factor_premia)
print(result.covariance_matrices)
# Generate HTML report
model.to_report("brisma_report.html")
Complete pipeline with all intermediate steps:
from brisma import (
load_data, create_mapping_tables, convert_to_excess_returns,
prepare_time_series, create_index_matrix_and_returns,
estimate_iterative_covariance, estimate_factor_model,
fit_garch_models, calculate_garch_covariance,
inverse_optimize_mv, extract_factor_premia_ols
)
# Step 1: Load Excel data (4 sheets)
data = load_data("data/brisma_data.xlsx")
mappings = create_mapping_tables(
data["tbl_client_portfolio"],
data["tbl_risk_model"]
)
# Step 2: Convert to excess returns (currency-adjusted)
excess = convert_to_excess_returns(
data["tbl_idx_data"], data["tbl_fx_data"],
data["tbl_client_portfolio"], data["tbl_risk_model"],
mappings["tbl_map_id_currency"], "EUR", cash_id
)
ts = prepare_time_series(excess, twi, data["tbl_client_portfolio"],
data["tbl_risk_model"], lookback_years=10)
# Step 3: Compute returns and covariance matrices
returns = create_index_matrix_and_returns(
ts["tbl_ts_data"], ts["start_date"], ts["end_date"],
ts["id"], ts["id_rm"], period_length=22
)
cov = estimate_iterative_covariance(
returns["ret22_date_id"], returns["idx_date_id"],
ts["id"], ts["id_rm"], 22
)
factor = estimate_factor_model(
cov["Q_rm_comp"], cov["Q_emp"], cov["ei"],
ts["id_port"], variance_threshold=0.95
)
# Step 4: Inverse optimization and factor premia
result = inverse_optimize_mv(weights, factor["Q_shrink"], lambda_=2.5, rf=0.02)
premia = extract_factor_premia_ols(result.mu, factor["beta_id_port_id_comp_fit"], rf=0.02)
print(f"Model R-squared: {premia.r_squared:.2%}")
# Run full pipeline with default settings
python -m brisma run data/brisma_data.xlsx -o outputs/
# Show help
python -m brisma --help
# Run tests
pytest tests/python/ --ignore=tests/python/test_data_loading.py -v
# Ensure you're in the brisma directory with venv activated
cd brisma
.venv\Scripts\Activate
pip install -e python/
# Use shrinkage-adjusted covariance instead of empirical
result = inverse_optimize_mv(weights, factor["Q_shrink"], lambda_=2.5)
# Normalize weights before optimization
weights = weights / weights.sum()
CLAUDE.md for development guidance.