Home

Getting Started with BRISMA

Extract implied factor risk premia from portfolio weights in 5 minutes

What is BRISMA?

BRISMA (Bantleon Risk Model Analysis) extracts implied factor risk premia from observed portfolio weights, then uses those premia to price new assets.

Python 3.13.5+
R 4.5.2
UV package manager

Core Capabilities

Pipeline: w + Q → μ* = λQw → π = (B'B)-1B'(μ*-rf) → μnew = rf + B'π

Installation

1

Clone Repository

git clone https://github.com/Digital-AI-Finance/implied-risk-premia.git
cd implied-risk-premia
2

Setup Python Environment

# 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
3

Verify Installation

python -c "import brisma; print(f'BRISMA v{brisma.__version__}')"
# Output: BRISMA v0.2.0

Key Dependencies

PackagePurpose
numpy, scipyNumerical computing, optimization
polarsFast DataFrames
archGARCH volatility models
cvxpyConvex optimization
plotlyInteractive charts

Quick Start (5 minutes)

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%}")
Expected Output:
Duration premium: ~0.5%/yr | Credit premium: ~1.5%/yr | New asset: ~5.5%

High-Level API (Recommended)

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")

Full Workflow (Production)

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%}")

CLI Usage

# 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

Next Steps

Troubleshooting

ModuleNotFoundError: brisma

# Ensure you're in the brisma directory with venv activated
cd brisma
.venv\Scripts\Activate
pip install -e python/

Covariance matrix not positive definite

# Use shrinkage-adjusted covariance instead of empirical
result = inverse_optimize_mv(weights, factor["Q_shrink"], lambda_=2.5)

Portfolio weights don't sum to 1

# Normalize weights before optimization
weights = weights / weights.sum()
Note: GARCH models require 500+ observations for reliable estimates. Use empirical or shrinkage covariance for shorter time series.
Help: See GitHub Issues for support or check CLAUDE.md for development guidance.