or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

constraints-advanced.mdhierarchical-clustering.mdindex.mdparameter-estimation.mdplotting-visualization.mdportfolio-optimization.mdreports.mdrisk-functions.md

constraints-advanced.mddocs/

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