- Published on
Portfolio Construction (NCO)
- Authors

- Name
- Tails Azimuth
Table of Contents
Portfolio Construction (NCO)
This chapter addresses a fundamental flaw in modern portfolio theory: instability. While Markowitz's (1952) mean-variance optimization is mathematically elegant, its solutions are notoriously unstable and concentrated, often underperforming simpler 1/N portfolios in practice.
This instability arises from two sources: noise (from data sampling) and, as this chapter details, signal (from the data's true structure).
The Problem: Markowitz's Curse
The classic Markowitz solution for a minimum-variance portfolio is a convex optimization problem:
- Objective Function:
- Constraint:
- The Solution:
The critical problem is that this solution requires inverting the covariance matrix (). This leads to Markowitz's Curse:
The more correlated the assets (i.e., the more diversification is needed), the more unstable and unreliable the solution becomes.
This instability is measured by the condition number of the correlation matrix, which is the ratio of its largest eigenvalue () to its smallest ().
- Example (2x2 Matrix): For a simple :
- The eigenvalues are and .
- The inverse is .
- As correlation , , the condition number () , and the inverse explodes. This numerical instability magnifies any small estimation errors in the inputs, resulting in extreme and volatile portfolio weights.
Signal as the Source of Instability
This instability is not just random noise; it is caused by the true signal structure of financial data: clusters.
- Financial assets are naturally clustered (e.g., by sector, industry, or style).
- Assets within a cluster are highly correlated with each other.
- This cluster structure creates large eigenvalues (representing the cluster) and forces other eigenvalues to be very small to compensate (since ).
- This guarantees a high condition number and an unstable matrix. While the instability is caused by a specific cluster, inverting the matrix spreads this instability across the entire portfolio solution.
The Solution: Nested Clustered Optimization (NCO)
The Nested Clustered Optimization (NCO) algorithm is an ML-based "wrapper" method that solves Markowitz's Curse by containing this instability. It splits the optimization into a three-step, hierarchical process.
Correlation Clustering:
- The covariance matrix is first denoised (using methods from Section 2, like Marcenko-Pastur) to remove random noise.
- A clustering algorithm (like the ONC algorithm from Section 4) is applied to the clean correlation matrix to identify the underlying cluster structure (e.g., the 10 sectors).
Intra-cluster Allocation:
- The optimization (e.g., minimum variance) is run separately for the assets within each cluster.
- This step "contains" the instability. The high correlations are handled locally, without affecting the rest of the portfolio.
- This produces a reduced covariance matrix (
cov2) that represents the risk/reward of the optimized clusters themselves.
Inter-cluster Allocation:
- The optimization is run a final time on the reduced covariance matrix (
cov2). - Because the clusters are (by definition) dissimilar, this reduced matrix is stable, well-conditioned (close to diagonal), and solves the "curse."
- The final portfolio weights are the product of the inter-cluster weights and the intra-cluster weights.
- The optimization is run a final time on the reduced covariance matrix (
Experimental Results
Monte Carlo experiments confirm NCO's superiority. When compared to Markowitz's solution (CLA) on a "true" covariance matrix with a 10-block cluster structure:
- For the Minimum Variance Portfolio, NCO reduced the allocation error (RMSE) by 47%.
- For the Maximum Sharpe Ratio Portfolio, NCO reduced the allocation error (RMSE) by 55%.
The NCO algorithm provides a substantially more robust and stable solution by isolating cluster-specific instability instead of allowing it to infect the entire portfolio.
API reference
RiskLabAI implements these in Python and Julia (signatures auto-generated from the package source):
| Python | Julia |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |