0
# Constraints and Advanced Features
1
2
Advanced constraint modeling including network constraints, clustering constraints, risk budgeting, factor risk constraints, and sophisticated portfolio construction techniques for institutional-level portfolio optimization.
3
4
## Capabilities
5
6
### Asset-Level Constraints
7
8
Functions for defining constraints on individual assets and asset groups.
9
10
```python { .api }
11
def assets_constraints(assets, min_val=None, max_val=None):
12
"""
13
Create asset-level constraints matrix.
14
15
Parameters:
16
- assets (list): List of asset names
17
- min_val (dict or float): Minimum weight constraints per asset
18
- max_val (dict or float): Maximum weight constraints per asset
19
20
Returns:
21
tuple: (A_matrix, B_vector) for inequality constraints Aw <= b
22
"""
23
24
def assets_views(P, Q):
25
"""
26
Create asset views matrix for Black-Litterman optimization.
27
28
Parameters:
29
- P (DataFrame): Picking matrix defining which assets each view affects
30
- Q (DataFrame): Views vector with expected returns for each view
31
32
Returns:
33
tuple: (P_matrix, Q_vector) formatted for optimization
34
"""
35
36
def assets_clusters(returns, codependence='pearson', linkage='ward', k=None, max_k=10):
37
"""
38
Create asset clustering constraints for hierarchical allocation.
39
40
Parameters:
41
- returns (DataFrame): Asset returns data
42
- codependence (str): Distance measure for clustering
43
- linkage (str): Linkage method for hierarchical clustering
44
- k (int): Number of clusters (optional, will optimize if None)
45
- max_k (int): Maximum number of clusters to consider
46
47
Returns:
48
DataFrame: Cluster assignment matrix
49
"""
50
```
51
52
### Factor-Level Constraints
53
54
Constraints based on risk factor models and factor exposures.
55
56
```python { .api }
57
def factors_constraints(B, factors, min_val=None, max_val=None):
58
"""
59
Create factor exposure constraints.
60
61
Parameters:
62
- B (DataFrame): Factor loadings matrix (assets x factors)
63
- factors (list): List of factor names to constrain
64
- min_val (dict or float): Minimum factor exposure limits
65
- max_val (dict or float): Maximum factor exposure limits
66
67
Returns:
68
tuple: (A_matrix, B_vector) for factor constraints
69
"""
70
71
def factors_views(B, P, Q):
72
"""
73
Create factor views for Black-Litterman factor model.
74
75
Parameters:
76
- B (DataFrame): Factor loadings matrix
77
- P (DataFrame): Factor picking matrix
78
- Q (DataFrame): Factor views vector
79
80
Returns:
81
tuple: (P_factor, Q_factor) for factor-based views
82
"""
83
```
84
85
### Risk Budgeting Constraints
86
87
Advanced risk budgeting and risk contribution constraints.
88
89
```python { .api }
90
def risk_constraint(w, cov, rm='MV', rf=0, alpha=0.05):
91
"""
92
Calculate risk constraint for portfolio optimization.
93
94
Parameters:
95
- w (DataFrame): Portfolio weights
96
- cov (DataFrame): Covariance matrix
97
- rm (str): Risk measure code
98
- rf (float): Risk-free rate
99
- alpha (float): Significance level for risk measures
100
101
Returns:
102
float: Risk constraint value
103
"""
104
105
def hrp_constraints(returns, codependence='pearson', covariance='hist',
106
linkage='single', max_k=10, leaf_order=True):
107
"""
108
Generate Hierarchical Risk Parity constraints.
109
110
Parameters:
111
- returns (DataFrame): Asset returns
112
- codependence (str): Codependence measure
113
- covariance (str): Covariance estimation method
114
- linkage (str): Linkage method for clustering
115
- max_k (int): Maximum number of clusters
116
- leaf_order (bool): Optimize dendrogram leaf order
117
118
Returns:
119
dict: HRP constraint specifications
120
"""
121
```
122
123
### Network Constraints
124
125
Constraints based on asset correlation networks and graph theory.
126
127
```python { .api }
128
def connection_matrix(returns, network_method='MST', codependence='pearson'):
129
"""
130
Generate network connection matrix based on asset correlations.
131
132
Parameters:
133
- returns (DataFrame): Asset returns data
134
- network_method (str): Network construction method ('MST', 'PMFG', 'TN', 'DBHT')
135
- codependence (str): Measure of dependence between assets
136
137
Returns:
138
DataFrame: Binary connection matrix (1 = connected, 0 = not connected)
139
"""
140
141
def centrality_vector(adjacency_matrix, centrality='degree'):
142
"""
143
Calculate centrality measures for network nodes.
144
145
Parameters:
146
- adjacency_matrix (DataFrame): Network adjacency matrix
147
- centrality (str): Centrality measure ('degree', 'betweenness', 'closeness', 'eigenvector')
148
149
Returns:
150
DataFrame: Centrality scores for each asset
151
"""
152
153
def clusters_matrix(returns, codependence='pearson', linkage='ward', k=None, max_k=10):
154
"""
155
Generate cluster assignment matrix for portfolio constraints.
156
157
Parameters:
158
- returns (DataFrame): Asset returns
159
- codependence (str): Codependence measure
160
- linkage (str): Hierarchical linkage method
161
- k (int): Number of clusters (will optimize if None)
162
- max_k (int): Maximum clusters to consider
163
164
Returns:
165
DataFrame: Binary cluster assignment matrix
166
"""
167
168
def average_centrality(adjacency_matrix, centrality='degree'):
169
"""
170
Calculate average network centrality.
171
172
Parameters:
173
- adjacency_matrix (DataFrame): Network adjacency matrix
174
- centrality (str): Centrality measure type
175
176
Returns:
177
float: Average centrality value
178
"""
179
180
def connected_assets(adjacency_matrix, asset):
181
"""
182
Find assets directly connected to a given asset in the network.
183
184
Parameters:
185
- adjacency_matrix (DataFrame): Network adjacency matrix
186
- asset (str): Asset name to find connections for
187
188
Returns:
189
list: List of directly connected asset names
190
"""
191
192
def related_assets(adjacency_matrix, asset, max_distance=2):
193
"""
194
Find assets related to a given asset within specified network distance.
195
196
Parameters:
197
- adjacency_matrix (DataFrame): Network adjacency matrix
198
- asset (str): Asset name to find relations for
199
- max_distance (int): Maximum network distance to consider
200
201
Returns:
202
dict: Dictionary mapping distance to list of assets at that distance
203
"""
204
```
205
206
### Advanced Constraint Types
207
208
Portfolio constraints for institutional and complex optimization scenarios.
209
210
```python { .api }
211
# Network SDP (Semi-Definite Programming) Constraints
212
def network_sdp_constraint(returns, network_method='MST', alpha=0.05):
213
"""
214
Generate network-based SDP constraints for portfolio optimization.
215
216
Parameters:
217
- returns (DataFrame): Asset returns
218
- network_method (str): Network construction method
219
- alpha (float): Network density parameter
220
221
Returns:
222
dict: SDP constraint specifications
223
"""
224
225
# Cluster SDP Constraints
226
def cluster_sdp_constraint(returns, k=5, linkage='ward', alpha=0.05):
227
"""
228
Generate cluster-based SDP constraints.
229
230
Parameters:
231
- returns (DataFrame): Asset returns
232
- k (int): Number of clusters
233
- linkage (str): Clustering linkage method
234
- alpha (float): Constraint strength parameter
235
236
Returns:
237
dict: Cluster SDP constraint specifications
238
"""
239
240
# Integer Programming Constraints
241
def network_ip_constraint(adjacency_matrix, max_assets=20):
242
"""
243
Generate network-based integer programming constraints.
244
245
Parameters:
246
- adjacency_matrix (DataFrame): Network adjacency matrix
247
- max_assets (int): Maximum number of assets to select
248
249
Returns:
250
dict: Integer programming constraint specifications
251
"""
252
253
def cluster_ip_constraint(cluster_matrix, max_per_cluster=5):
254
"""
255
Generate cluster-based integer programming constraints.
256
257
Parameters:
258
- cluster_matrix (DataFrame): Cluster assignment matrix
259
- max_per_cluster (int): Maximum assets per cluster
260
261
Returns:
262
dict: Cluster IP constraint specifications
263
"""
264
```
265
266
## Usage Examples
267
268
### Basic Asset Constraints
269
270
```python
271
import riskfolio as rp
272
import pandas as pd
273
274
# Load returns
275
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
276
assets = returns.columns.tolist()
277
278
# Create basic asset constraints
279
# Minimum 1%, maximum 10% per asset
280
A, b = rp.assets_constraints(
281
assets=assets,
282
min_val=0.01, # 1% minimum
283
max_val=0.10 # 10% maximum
284
)
285
286
# Create portfolio with constraints
287
port = rp.Portfolio(returns=returns)
288
port.assets_stats()
289
290
# Set constraints
291
port.ainequality = A
292
port.binequality = b
293
294
# Optimize with constraints
295
w = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)
296
297
print("Constrained Portfolio Weights:")
298
print(w.sort_values('weights', ascending=False).head(10))
299
```
300
301
### Sector/Group Constraints
302
303
```python
304
import riskfolio as rp
305
import pandas as pd
306
import numpy as np
307
308
# Load returns and sector mapping
309
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
310
sectors = pd.read_csv('sectors.csv', index_col=0) # Asset -> Sector mapping
311
312
# Create sector constraints (max 30% per sector)
313
sector_names = sectors['Sector'].unique()
314
A_sector = []
315
b_sector = []
316
317
for sector in sector_names:
318
sector_assets = sectors[sectors['Sector'] == sector].index
319
# Create constraint row: sum of weights in sector <= 0.30
320
constraint_row = pd.Series(0.0, index=returns.columns)
321
constraint_row[sector_assets] = 1.0
322
A_sector.append(constraint_row)
323
b_sector.append(0.30)
324
325
A_sector = pd.DataFrame(A_sector)
326
b_sector = pd.Series(b_sector)
327
328
# Apply to portfolio
329
port = rp.Portfolio(returns=returns)
330
port.assets_stats()
331
port.ainequality = A_sector
332
port.binequality = b_sector
333
334
w_sector = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)
335
336
# Analyze sector allocation
337
sector_allocation = w_sector.groupby(sectors['Sector']).sum()
338
print("Sector Allocation:")
339
print(sector_allocation.sort_values('weights', ascending=False))
340
```
341
342
### Factor Model Constraints
343
344
```python
345
import riskfolio as rp
346
import pandas as pd
347
348
# Load returns and factors
349
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
350
factors = pd.read_csv('factors.csv', index_col=0, parse_dates=True)
351
352
# Estimate factor loadings
353
B = rp.loadings_matrix(X=returns, Y=factors, method='stepwise')
354
355
# Create factor constraints (e.g., market beta between 0.8 and 1.2)
356
factor_constraints = {
357
'Market': {'min': 0.8, 'max': 1.2},
358
'Value': {'min': -0.5, 'max': 0.5},
359
'Size': {'min': -0.3, 'max': 0.3}
360
}
361
362
A_factors = []
363
b_factors = []
364
365
for factor, bounds in factor_constraints.items():
366
if factor in B.columns:
367
# Beta <= max constraint
368
A_factors.append(B[factor])
369
b_factors.append(bounds['max'])
370
371
# Beta >= min constraint (converted to -Beta <= -min)
372
A_factors.append(-B[factor])
373
b_factors.append(-bounds['min'])
374
375
A_factors = pd.DataFrame(A_factors).T
376
b_factors = pd.Series(b_factors)
377
378
# Apply factor constraints
379
port = rp.Portfolio(returns=returns, factors=factors)
380
port.factors_stats()
381
port.B = B
382
port.afrcinequality = A_factors
383
port.bfrcinequality = b_factors
384
385
w_factor = port.optimization(model='FM', rm='MV', obj='Sharpe', rf=0.02)
386
387
# Check factor exposures
388
factor_exposures = (w_factor.T @ B).T
389
print("Factor Exposures:")
390
print(factor_exposures)
391
```
392
393
### Network-Based Constraints
394
395
```python
396
import riskfolio as rp
397
import pandas as pd
398
399
# Load returns
400
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
401
402
# Generate network connection matrix
403
adjacency = rp.connection_matrix(
404
returns=returns,
405
network_method='MST',
406
codependence='pearson'
407
)
408
409
# Calculate centrality measures
410
centrality = rp.centrality_vector(
411
adjacency_matrix=adjacency,
412
centrality='degree'
413
)
414
415
# Create centrality-based constraints (limit high centrality assets)
416
# Assets with high centrality (>75th percentile) limited to 5% each
417
high_centrality_threshold = centrality['centrality'].quantile(0.75)
418
high_centrality_assets = centrality[centrality['centrality'] > high_centrality_threshold].index
419
420
A_centrality = []
421
b_centrality = []
422
423
for asset in high_centrality_assets:
424
constraint_row = pd.Series(0.0, index=returns.columns)
425
constraint_row[asset] = 1.0
426
A_centrality.append(constraint_row)
427
b_centrality.append(0.05) # 5% limit
428
429
if A_centrality: # Only if there are high centrality assets
430
A_centrality = pd.DataFrame(A_centrality)
431
b_centrality = pd.Series(b_centrality)
432
433
# Apply network constraints
434
port = rp.Portfolio(returns=returns)
435
port.assets_stats()
436
port.acentrality = A_centrality
437
port.bcentrality = b_centrality
438
439
w_network = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.02)
440
441
print("Network-Constrained Portfolio:")
442
print(w_network.sort_values('weights', ascending=False).head(10))
443
444
# Find connected assets for top holdings
445
top_asset = w_network.sort_values('weights', ascending=False).index[0]
446
connected = rp.connected_assets(adjacency, top_asset)
447
print(f"\nAssets connected to {top_asset}: {connected}")
448
```
449
450
### Risk Budgeting Implementation
451
452
```python
453
import riskfolio as rp
454
import pandas as pd
455
import numpy as np
456
457
# Load returns
458
returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)
459
460
# Create risk budget vector (equal risk contribution desired)
461
n_assets = len(returns.columns)
462
risk_budget = pd.DataFrame(1/n_assets, index=returns.columns, columns=['budget'])
463
464
# Create portfolio for risk parity
465
port = rp.Portfolio(returns=returns)
466
port.assets_stats()
467
468
# Risk parity optimization
469
w_rp = port.rp_optimization(
470
model='Classic',
471
rm='MV',
472
b=risk_budget,
473
rf=0.02
474
)
475
476
# Calculate actual risk contributions
477
risk_contrib = rp.Risk_Contribution(w_rp, port.cov, rm='MV')
478
479
print("Risk Parity Portfolio:")
480
print("Weights vs Risk Contributions:")
481
comparison = pd.DataFrame({
482
'Weights': w_rp['weights'],
483
'Risk_Contrib': risk_contrib['Risk'],
484
'Target_Budget': risk_budget['budget']
485
})
486
print(comparison.head(10))
487
488
# Custom risk budgets (e.g., sector-based)
489
# Suppose we want 40% risk from tech, 30% from finance, 30% from others
490
sectors = pd.read_csv('sectors.csv', index_col=0)
491
custom_budget = pd.Series(0.0, index=returns.columns)
492
493
tech_assets = sectors[sectors['Sector'] == 'Technology'].index
494
finance_assets = sectors[sectors['Sector'] == 'Finance'].index
495
other_assets = sectors[~sectors['Sector'].isin(['Technology', 'Finance'])].index
496
497
custom_budget[tech_assets] = 0.40 / len(tech_assets)
498
custom_budget[finance_assets] = 0.30 / len(finance_assets)
499
custom_budget[other_assets] = 0.30 / len(other_assets)
500
501
custom_budget_df = pd.DataFrame(custom_budget, columns=['budget'])
502
503
# Optimize with custom risk budget
504
w_custom_rp = port.rp_optimization(
505
model='Classic',
506
rm='CVaR',
507
b=custom_budget_df,
508
rf=0.02
509
)
510
511
print("\nCustom Risk Budget Results:")
512
print(w_custom_rp.head(10))
513
```
514
515
## Constraint Types Summary
516
517
### Linear Constraints
518
- **Asset bounds**: Minimum and maximum weights per asset
519
- **Group constraints**: Sector, geographic, or custom grouping limits
520
- **Turnover constraints**: Limits on portfolio changes from previous period
521
522
### Risk Constraints
523
- **Risk budgeting**: Target risk contributions by asset or group
524
- **Factor exposure**: Limits on systematic risk factor loadings
525
- **Tracking error**: Maximum deviation from benchmark
526
527
### Network Constraints
528
- **Connectivity**: Constraints based on asset correlation networks
529
- **Centrality**: Limits on highly connected (systemic) assets
530
- **Clustering**: Group-based allocation using hierarchical clustering
531
532
### Advanced Constraints
533
- **SDP constraints**: Semi-definite programming for complex risk structures
534
- **Integer constraints**: Asset selection and cardinality constraints
535
- **Transaction costs**: Optimization including realistic trading costs