Skip to main content

correlation risk - learned the hard way

lost $1,400 yesterday because i didn’t track correlation between my positions. dumb mistake.

what happened
#

may 10, 2pm:

  • had 3 open credit spreads
  • all “independent” strategies
  • all on tech stocks

market dumped 2%. all 3 positions hit stops at same time.

total loss: $1,400

should’ve been $400-500 max (one position losing).

the problem
#

my 3 “independent” positions:

  1. QQQ put spread (tech ETF)
  2. AAPL put spread (tech stock)
  3. MSFT put spread (tech stock)

thought i was diversified. i wasn’t.

QQQ is 40% AAPL + MSFT. when tech dumps, all 3 move together.

correlation = 0.92 between these positions. might as well have been the same trade 3 times.

what i should’ve tracked
#

import pandas as pd
import numpy as np

# Get position correlations
positions = ['QQQ', 'AAPL', 'MSFT']
returns = get_historical_returns(positions, days=30)

correlation_matrix = returns.corr()
print(correlation_matrix)

#      QQQ   AAPL  MSFT
# QQQ  1.00  0.89  0.91
# AAPL 0.89  1.00  0.93
# MSFT 0.91  0.93  1.00

# All highly correlated = not diversified

correlation > 0.7 = basically same position

i had 0.92. stupid.

position limits i should’ve had
#

by sector:

  • max 2 positions in same sector
  • max 40% of capital in correlated positions (>0.7)

by underlying:

  • max 1 position per stock
  • ETFs count as multiple stocks (check holdings)

by strategy:

  • even “different” strategies can correlate
  • premium selling on correlated underlyings = same risk

how to actually diversify
#

good diversification:

  1. SPX put spread (broad market)
  2. TLT call spread (bonds - negative correlation to stocks)
  3. GLD put spread (gold - low correlation)

correlation matrix:

     SPX   TLT   GLD
SPX  1.00 -0.45  0.12
TLT -0.45  1.00 -0.18
GLD  0.12 -0.18  1.00

negative correlation to TLT = when stocks dump, bonds rally, TLT position makes money.

code i added
#

class PositionManager:
    def __init__(self):
        self.positions = []
        self.max_correlation = 0.70
        self.max_sector_exposure = 0.40

    def check_correlation(self, new_position):
        """Check if new position is too correlated with existing"""
        if len(self.positions) == 0:
            return True

        # Get symbols of current positions
        current_symbols = [p.underlying for p in self.positions]

        # Calculate correlation with new position
        all_symbols = current_symbols + [new_position.underlying]
        returns = self.get_returns(all_symbols, days=30)
        corr_matrix = returns.corr()

        # Check correlation with each existing position
        for symbol in current_symbols:
            correlation = corr_matrix.loc[symbol, new_position.underlying]
            if abs(correlation) > self.max_correlation:
                print(f"REJECTED: {new_position.underlying} correlation {correlation:.2f} with {symbol}")
                return False

        return True

    def check_sector_exposure(self, new_position):
        """Check if adding position exceeds sector limits"""
        sector = self.get_sector(new_position.underlying)

        # Calculate current sector exposure
        sector_capital = sum(p.capital_at_risk for p in self.positions
                            if self.get_sector(p.underlying) == sector)
        total_capital = self.account_value

        new_exposure = (sector_capital + new_position.capital_at_risk) / total_capital

        if new_exposure > self.max_sector_exposure:
            print(f"REJECTED: {sector} exposure {new_exposure:.1%} exceeds {self.max_sector_exposure:.1%}")
            return False

        return True

    def add_position(self, position):
        """Add position after checking correlation and sector limits"""
        if not self.check_correlation(position):
            return False

        if not self.check_sector_exposure(position):
            return False

        self.positions.append(position)
        return True

what this would’ve prevented
#

may 10 scenario with correlation checking:

  1. add QQQ put spread ✅ (no existing positions)
  2. add AAPL put spread ❌ (correlation 0.89 with QQQ, rejected)
  3. add MSFT put spread ❌ (correlation 0.91 with QQQ, rejected)

would’ve only had 1 position in tech. loss: $400 instead of $1,400.

saved $1,000.

other correlation mistakes
#

strategies that correlate more than you think:

  • short volatility strategies (all lose when VIX spikes)
  • premium selling (all lose in crashes)
  • mean reversion (all lose in trends)
  • momentum (all lose in reversals)

even “different” strategies can correlate during market stress.

may performance so far
#

week 1: +$520 week 2 (so far): -$1,400

net may: -$880

back below break-even. frustrated but it’s a good lesson.

better to lose $1,400 learning about correlation now than $10k later when account is bigger.

what i’m changing
#

  1. added correlation checking to position manager (code above)
  2. max 2 positions per sector
  3. target negative correlation between positions
  4. checking correlation weekly, not just at entry
  5. if correlation > 0.7 develops during hold, close one position

sectors i’m using for limits
#

  • technology (QQQ, AAPL, MSFT, GOOGL, etc)
  • financials (JPM, BAC, XLF, etc)
  • healthcare (JNJ, UNH, XLV, etc)
  • energy (XLE, CVX, XOM, etc)
  • bonds (TLT, IEF, AGG)
  • commodities (GLD, SLV, USO)

testing the new system
#

backtesting last 30 days with correlation limits:

  • original: 14 trades, -$880 net
  • with limits: 9 trades (5 rejected), +$340 net

correlation checking would’ve prevented all my correlated losses.

the math
#

without correlation limits:

  • 3 positions @ $400 risk each = $1,200 total risk
  • correlation 0.92 means effective risk = $1,100 (not $400)
  • actual loss: $1,400 (worse than expected)

with correlation limits:

  • 3 positions with correlation < 0.3
  • effective risk = $650 (true diversification benefit)
  • expected max loss = $700 (much better)

lesson
#

diversification isn’t about number of positions. it’s about correlation between positions.

10 positions in tech = 1 position 3 positions across uncorrelated sectors = actual diversification


3:15am. expensive lesson but necessary. correlation checking goes live tomorrow.

-AK

Related

may day 1 - smaller position sizes
cut position sizes in half. only trading 3 strategies now. first day of may went… fine? +$180 on one trade. nothing else triggered. new risk rules # old position sizing: 2.5% risk per trade = $850 max loss (on $340k account)
actual risk management rules that work
after losing $60k in 4 months i finally built risk management that doesn’t suck. what wasn’t working # my “risk management” before:
position sizing is killing me - fixing it with code
been up since 3am coding a proper position sizing module. my current approach (fixed 2% risk per trade) is bleeding me out. down another $8k this week. total drawdown now -$48k since january. at this rate i’ll be broke by august.