CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-riskfolio-lib

Portfolio Optimization and Quantitative Strategic Asset Allocation in Python

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

constraints-advanced.mddocs/

Constraints and Advanced Features

Advanced constraint modeling including network constraints, clustering constraints, risk budgeting, factor risk constraints, and sophisticated portfolio construction techniques for institutional-level portfolio optimization.

Capabilities

Asset-Level Constraints

Functions for defining constraints on individual assets and asset groups.

def assets_constraints(assets, min_val=None, max_val=None):
    """
    Create asset-level constraints matrix.
    
    Parameters:
    - assets (list): List of asset names
    - min_val (dict or float): Minimum weight constraints per asset
    - max_val (dict or float): Maximum weight constraints per asset
    
    Returns:
    tuple: (A_matrix, B_vector) for inequality constraints Aw <= b
    """

def assets_views(P, Q):
    """
    Create asset views matrix for Black-Litterman optimization.
    
    Parameters:
    - P (DataFrame): Picking matrix defining which assets each view affects
    - Q (DataFrame): Views vector with expected returns for each view
    
    Returns:
    tuple: (P_matrix, Q_vector) formatted for optimization
    """

def assets_clusters(returns, codependence='pearson', linkage='ward', k=None, max_k=10):
    """
    Create asset clustering constraints for hierarchical allocation.
    
    Parameters:
    - returns (DataFrame): Asset returns data
    - codependence (str): Distance measure for clustering
    - linkage (str): Linkage method for hierarchical clustering
    - k (int): Number of clusters (optional, will optimize if None)
    - max_k (int): Maximum number of clusters to consider
    
    Returns:
    DataFrame: Cluster assignment matrix
    """

Factor-Level Constraints

Constraints based on risk factor models and factor exposures.

def factors_constraints(B, factors, min_val=None, max_val=None):
    """
    Create factor exposure constraints.
    
    Parameters:
    - B (DataFrame): Factor loadings matrix (assets x factors)
    - factors (list): List of factor names to constrain
    - min_val (dict or float): Minimum factor exposure limits
    - max_val (dict or float): Maximum factor exposure limits
    
    Returns:
    tuple: (A_matrix, B_vector) for factor constraints
    """

def factors_views(B, P, Q):
    """
    Create factor views for Black-Litterman factor model.
    
    Parameters:
    - B (DataFrame): Factor loadings matrix
    - P (DataFrame): Factor picking matrix
    - Q (DataFrame): Factor views vector
    
    Returns:
    tuple: (P_factor, Q_factor) for factor-based views
    """

Risk Budgeting Constraints

Advanced risk budgeting and risk contribution constraints.

def risk_constraint(w, cov, rm='MV', rf=0, alpha=0.05):
    """
    Calculate risk constraint for portfolio optimization.
    
    Parameters:
    - w (DataFrame): Portfolio weights
    - cov (DataFrame): Covariance matrix
    - rm (str): Risk measure code
    - rf (float): Risk-free rate
    - alpha (float): Significance level for risk measures
    
    Returns:
    float: Risk constraint value
    """

def hrp_constraints(returns, codependence='pearson', covariance='hist', 
                   linkage='single', max_k=10, leaf_order=True):
    """
    Generate Hierarchical Risk Parity constraints.
    
    Parameters:
    - returns (DataFrame): Asset returns
    - codependence (str): Codependence measure
    - covariance (str): Covariance estimation method
    - linkage (str): Linkage method for clustering
    - max_k (int): Maximum number of clusters
    - leaf_order (bool): Optimize dendrogram leaf order
    
    Returns:
    dict: HRP constraint specifications
    """

Network Constraints

Constraints based on asset correlation networks and graph theory.

def connection_matrix(returns, network_method='MST', codependence='pearson'):
    """
    Generate network connection matrix based on asset correlations.
    
    Parameters:
    - returns (DataFrame): Asset returns data
    - network_method (str): Network construction method ('MST', 'PMFG', 'TN', 'DBHT')
    - codependence (str): Measure of dependence between assets
    
    Returns:
    DataFrame: Binary connection matrix (1 = connected, 0 = not connected)
    """

def centrality_vector(adjacency_matrix, centrality='degree'):
    """
    Calculate centrality measures for network nodes.
    
    Parameters:
    - adjacency_matrix (DataFrame): Network adjacency matrix
    - centrality (str): Centrality measure ('degree', 'betweenness', 'closeness', 'eigenvector')
    
    Returns:
    DataFrame: Centrality scores for each asset
    """

def clusters_matrix(returns, codependence='pearson', linkage='ward', k=None, max_k=10):
    """
    Generate cluster assignment matrix for portfolio constraints.
    
    Parameters:
    - returns (DataFrame): Asset returns
    - codependence (str): Codependence measure
    - linkage (str): Hierarchical linkage method
    - k (int): Number of clusters (will optimize if None)
    - max_k (int): Maximum clusters to consider
    
    Returns:
    DataFrame: Binary cluster assignment matrix
    """

def average_centrality(adjacency_matrix, centrality='degree'):
    """
    Calculate average network centrality.
    
    Parameters:
    - adjacency_matrix (DataFrame): Network adjacency matrix
    - centrality (str): Centrality measure type
    
    Returns:
    float: Average centrality value
    """

def connected_assets(adjacency_matrix, asset):
    """
    Find assets directly connected to a given asset in the network.
    
    Parameters:
    - adjacency_matrix (DataFrame): Network adjacency matrix
    - asset (str): Asset name to find connections for
    
    Returns:
    list: List of directly connected asset names
    """

def related_assets(adjacency_matrix, asset, max_distance=2):
    """
    Find assets related to a given asset within specified network distance.
    
    Parameters:
    - adjacency_matrix (DataFrame): Network adjacency matrix
    - asset (str): Asset name to find relations for
    - max_distance (int): Maximum network distance to consider
    
    Returns:
    dict: Dictionary mapping distance to list of assets at that distance
    """

Advanced Constraint Types

Portfolio constraints for institutional and complex optimization scenarios.

# Network SDP (Semi-Definite Programming) Constraints
def network_sdp_constraint(returns, network_method='MST', alpha=0.05):
    """
    Generate network-based SDP constraints for portfolio optimization.
    
    Parameters:
    - returns (DataFrame): Asset returns
    - network_method (str): Network construction method
    - alpha (float): Network density parameter
    
    Returns:
    dict: SDP constraint specifications
    """

# Cluster SDP Constraints  
def cluster_sdp_constraint(returns, k=5, linkage='ward', alpha=0.05):
    """
    Generate cluster-based SDP constraints.
    
    Parameters:
    - returns (DataFrame): Asset returns
    - k (int): Number of clusters
    - linkage (str): Clustering linkage method
    - alpha (float): Constraint strength parameter
    
    Returns:
    dict: Cluster SDP constraint specifications
    """

# Integer Programming Constraints
def network_ip_constraint(adjacency_matrix, max_assets=20):
    """
    Generate network-based integer programming constraints.
    
    Parameters:
    - adjacency_matrix (DataFrame): Network adjacency matrix
    - max_assets (int): Maximum number of assets to select
    
    Returns:
    dict: Integer programming constraint specifications
    """

def cluster_ip_constraint(cluster_matrix, max_per_cluster=5):
    """
    Generate cluster-based integer programming constraints.
    
    Parameters:
    - cluster_matrix (DataFrame): Cluster assignment matrix
    - max_per_cluster (int): Maximum assets per cluster
    
    Returns:
    dict: Cluster IP constraint specifications
    """

Usage Examples

Basic Asset Constraints

import riskfolio as rp
import pandas as pd

# Load returns
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
assets = returns.columns.tolist()

# Create basic asset constraints
# Minimum 1%, maximum 10% per asset
A, b = rp.assets_constraints(
    assets=assets,
    min_val=0.01,  # 1% minimum
    max_val=0.10   # 10% maximum
)

# Create portfolio with constraints
port = rp.Portfolio(returns=returns)
port.assets_stats()

# Set constraints
port.ainequality = A
port.binequality = b

# Optimize with constraints
w = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)

print("Constrained Portfolio Weights:")
print(w.sort_values('weights', ascending=False).head(10))

Sector/Group Constraints

import riskfolio as rp
import pandas as pd
import numpy as np

# Load returns and sector mapping
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
sectors = pd.read_csv('sectors.csv', index_col=0)  # Asset -> Sector mapping

# Create sector constraints (max 30% per sector)
sector_names = sectors['Sector'].unique()
A_sector = []
b_sector = []

for sector in sector_names:
    sector_assets = sectors[sectors['Sector'] == sector].index
    # Create constraint row: sum of weights in sector <= 0.30
    constraint_row = pd.Series(0.0, index=returns.columns)
    constraint_row[sector_assets] = 1.0
    A_sector.append(constraint_row)
    b_sector.append(0.30)

A_sector = pd.DataFrame(A_sector)
b_sector = pd.Series(b_sector)

# Apply to portfolio
port = rp.Portfolio(returns=returns)
port.assets_stats()
port.ainequality = A_sector
port.binequality = b_sector

w_sector = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)

# Analyze sector allocation
sector_allocation = w_sector.groupby(sectors['Sector']).sum()
print("Sector Allocation:")
print(sector_allocation.sort_values('weights', ascending=False))

Factor Model Constraints

import riskfolio as rp
import pandas as pd

# Load returns and factors
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
factors = pd.read_csv('factors.csv', index_col=0, parse_dates=True)

# Estimate factor loadings
B = rp.loadings_matrix(X=returns, Y=factors, method='stepwise')

# Create factor constraints (e.g., market beta between 0.8 and 1.2)
factor_constraints = {
    'Market': {'min': 0.8, 'max': 1.2},
    'Value': {'min': -0.5, 'max': 0.5},
    'Size': {'min': -0.3, 'max': 0.3}
}

A_factors = []
b_factors = []

for factor, bounds in factor_constraints.items():
    if factor in B.columns:
        # Beta <= max constraint
        A_factors.append(B[factor])
        b_factors.append(bounds['max'])
        
        # Beta >= min constraint (converted to -Beta <= -min)
        A_factors.append(-B[factor])
        b_factors.append(-bounds['min'])

A_factors = pd.DataFrame(A_factors).T
b_factors = pd.Series(b_factors)

# Apply factor constraints
port = rp.Portfolio(returns=returns, factors=factors)
port.factors_stats()
port.B = B
port.afrcinequality = A_factors
port.bfrcinequality = b_factors

w_factor = port.optimization(model='FM', rm='MV', obj='Sharpe', rf=0.02)

# Check factor exposures
factor_exposures = (w_factor.T @ B).T
print("Factor Exposures:")
print(factor_exposures)

Network-Based Constraints

import riskfolio as rp
import pandas as pd

# Load returns
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)

# Generate network connection matrix
adjacency = rp.connection_matrix(
    returns=returns,
    network_method='MST',
    codependence='pearson'
)

# Calculate centrality measures
centrality = rp.centrality_vector(
    adjacency_matrix=adjacency,
    centrality='degree'
)

# Create centrality-based constraints (limit high centrality assets)
# Assets with high centrality (>75th percentile) limited to 5% each
high_centrality_threshold = centrality['centrality'].quantile(0.75)
high_centrality_assets = centrality[centrality['centrality'] > high_centrality_threshold].index

A_centrality = []
b_centrality = []

for asset in high_centrality_assets:
    constraint_row = pd.Series(0.0, index=returns.columns)
    constraint_row[asset] = 1.0
    A_centrality.append(constraint_row)
    b_centrality.append(0.05)  # 5% limit

if A_centrality:  # Only if there are high centrality assets
    A_centrality = pd.DataFrame(A_centrality)
    b_centrality = pd.Series(b_centrality)
    
    # Apply network constraints
    port = rp.Portfolio(returns=returns)
    port.assets_stats()
    port.acentrality = A_centrality
    port.bcentrality = b_centrality
    
    w_network = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)
    
    print("Network-Constrained Portfolio:")
    print(w_network.sort_values('weights', ascending=False).head(10))
    
    # Find connected assets for top holdings
    top_asset = w_network.sort_values('weights', ascending=False).index[0]
    connected = rp.connected_assets(adjacency, top_asset)
    print(f"\nAssets connected to {top_asset}: {connected}")

Risk Budgeting Implementation

import riskfolio as rp
import pandas as pd
import numpy as np

# Load returns
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)

# Create risk budget vector (equal risk contribution desired)
n_assets = len(returns.columns)
risk_budget = pd.DataFrame(1/n_assets, index=returns.columns, columns=['budget'])

# Create portfolio for risk parity
port = rp.Portfolio(returns=returns)
port.assets_stats()

# Risk parity optimization
w_rp = port.rp_optimization(
    model='Classic',
    rm='MV',
    b=risk_budget,
    rf=0.02
)

# Calculate actual risk contributions
risk_contrib = rp.Risk_Contribution(w_rp, port.cov, rm='MV')

print("Risk Parity Portfolio:")
print("Weights vs Risk Contributions:")
comparison = pd.DataFrame({
    'Weights': w_rp['weights'],
    'Risk_Contrib': risk_contrib['Risk'],
    'Target_Budget': risk_budget['budget']
})
print(comparison.head(10))

# Custom risk budgets (e.g., sector-based)
# Suppose we want 40% risk from tech, 30% from finance, 30% from others
sectors = pd.read_csv('sectors.csv', index_col=0)
custom_budget = pd.Series(0.0, index=returns.columns)

tech_assets = sectors[sectors['Sector'] == 'Technology'].index
finance_assets = sectors[sectors['Sector'] == 'Finance'].index
other_assets = sectors[~sectors['Sector'].isin(['Technology', 'Finance'])].index

custom_budget[tech_assets] = 0.40 / len(tech_assets)
custom_budget[finance_assets] = 0.30 / len(finance_assets) 
custom_budget[other_assets] = 0.30 / len(other_assets)

custom_budget_df = pd.DataFrame(custom_budget, columns=['budget'])

# Optimize with custom risk budget
w_custom_rp = port.rp_optimization(
    model='Classic',
    rm='CVaR',
    b=custom_budget_df,
    rf=0.02
)

print("\nCustom Risk Budget Results:")
print(w_custom_rp.head(10))

Constraint Types Summary

Linear Constraints

  • Asset bounds: Minimum and maximum weights per asset
  • Group constraints: Sector, geographic, or custom grouping limits
  • Turnover constraints: Limits on portfolio changes from previous period

Risk Constraints

  • Risk budgeting: Target risk contributions by asset or group
  • Factor exposure: Limits on systematic risk factor loadings
  • Tracking error: Maximum deviation from benchmark

Network Constraints

  • Connectivity: Constraints based on asset correlation networks
  • Centrality: Limits on highly connected (systemic) assets
  • Clustering: Group-based allocation using hierarchical clustering

Advanced Constraints

  • SDP constraints: Semi-definite programming for complex risk structures
  • Integer constraints: Asset selection and cardinality constraints
  • Transaction costs: Optimization including realistic trading costs

Install with Tessl CLI

npx tessl i tessl/pypi-riskfolio-lib

docs

constraints-advanced.md

hierarchical-clustering.md

index.md

parameter-estimation.md

plotting-visualization.md

portfolio-optimization.md

reports.md

risk-functions.md

tile.json