You might own 10 stocks and feel diversified. But if 7 of them are tech stocks that move together, you're not. Correlation measures how much holdings move in lockstep. Concentration measures weight in any one sector or theme. Both can be dangerously high without you noticing — until a sector rotates. This part automates correlation and concentration analysis.

ℹ️ Not financial advice. Correlation is backward-looking and breaks down during market crises. Use as a risk assessment tool, not as certainty.

Correlation Matrix

Build a correlation matrix between your holdings to find which positions move together:

import yfinance as yf
import pandas as pd

def correlation_matrix(positions: list, period: str = "6mo") -> pd.DataFrame:
    tickers = [p["ticker"] for p in positions]
    prices = yf.download(tickers, period=period, auto_adjust=True, progress=False)["Close"]
    return prices.pct_change().dropna().corr().round(3)

def high_correlation_pairs(corr_matrix: pd.DataFrame, threshold: float = 0.75) -> list:
    tickers = corr_matrix.columns.tolist()
    pairs = []
    for i in range(len(tickers)):
        for j in range(i + 1, len(tickers)):
            val = corr_matrix.iloc[i, j]
            if abs(val) >= threshold:
                pairs.append({"ticker_a": tickers[i], "ticker_b": tickers[j],
                               "correlation": round(val, 3),
                               "direction": "positive" if val > 0 else "negative"})
    return sorted(pairs, key=lambda x: abs(x["correlation"]), reverse=True)

Sector Concentration & HHI

Calculate sector-level concentration and compute the Herfindahl-Hirschman Index (HHI) for overall diversification:

def sector_concentration(positions: list, current_prices: dict) -> dict:
    sector_values = {}
    total_value = 0
    for p in positions:
        value = p["shares"] * current_prices.get(p["ticker"], p["avg_cost"])
        sector = p.get("sector", "Unknown")
        sector_values[sector] = sector_values.get(sector, 0) + value
        total_value += value
    weights = {s: round(v / total_value * 100, 2) for s, v in sector_values.items()}
    weights_sorted = dict(sorted(weights.items(), key=lambda x: x[1], reverse=True))
    hhi = sum((w / 100) ** 2 for w in weights.values())
    return {
        "by_sector": weights_sorted,
        "hhi": round(hhi, 4),
        "concentration_score": "high" if hhi > 0.25 else "moderate" if hhi > 0.15 else "diversified",
        "top_sector": max(weights, key=weights.get),
        "top_sector_pct": max(weights.values()),
    }

Portfolio Beta

Measure portfolio sensitivity to overall market movements (SPY):

def portfolio_beta(positions: list, benchmark: str = "SPY", period: str = "1y") -> dict:
    tickers = [p["ticker"] for p in positions] + [benchmark]
    prices = yf.download(tickers, period=period, auto_adjust=True, progress=False)["Close"]
    returns = prices.pct_change().dropna()
    bench = returns[benchmark]
    betas = {}
    for p in positions:
        t = p["ticker"]
        if t in returns.columns:
            cov = returns[t].cov(bench)
            betas[t] = round(cov / bench.var(), 3) if bench.var() > 0 else 1.0
    total_val = sum(p["shares"] * float(prices[p["ticker"]].iloc[-1]) for p in positions)
    port_beta = sum(
        betas.get(p["ticker"], 1.0) * (p["shares"] * float(prices[p["ticker"]].iloc[-1])) / total_val
        for p in positions
    )
    return {
        "individual_betas": betas,
        "portfolio_beta": round(port_beta, 3),
        "interpretation": "aggressive" if port_beta > 1.2 else "defensive" if port_beta < 0.8 else "market-like",
    }

Weekly Monitoring

Set up a weekly analysis automation:

name: correlation_monitor
schedule: "0 8 * * 1"
steps:
  - load_positions:
      file: positions.yaml
  - fetch_prices:
      period: "6mo"
  - correlation_matrix: {}
  - high_correlation_pairs:
      threshold: 0.75
  - sector_concentration: {}
  - portfolio_beta:
      benchmark: SPY
  - alert_if:
      conditions:
        - metric: top_sector_pct
          threshold: 40
          message: "Single sector exceeds 40% of portfolio"
  - notify:
      subject: "🔗 Weekly Risk: {{ top_sector }}={{ top_pct }}% | Beta={{ portfolio_beta }}"

Key Metrics Explained

Metric Interpretation
Correlation > 0.75 High positive correlation. Holdings move very much in lockstep.
HHI < 0.15 Diversified. Concentration risk is low.
HHI 0.15–0.25 Moderate. Some concentration present.
HHI > 0.25 Concentrated. Single position or sector dominates.
Portfolio Beta > 1.2 Aggressive. More volatile than the market.
Portfolio Beta < 0.8 Defensive. Less volatile than the market.

Frequently Asked Questions

Part 3 FAQs

What's a dangerous sector concentration level?
Above 40% in a single sector is meaningful concentration risk. Above 50% is aggressive.
Does correlation stay constant?
No. Assets that appear uncorrelated in normal markets often correlate strongly during selloffs — this is called correlation breakdown risk.
What does the HHI score mean?
Below 0.15 = diversified, 0.15–0.25 = moderate, above 0.25 = concentrated.

Next: Move to Part 4 to build a professional risk metrics dashboard — Risk Metrics Dashboard (VaR, Sharpe, Beta).