$npx -y skills add agiprolabs/claude-trading-skills --skill correlation-analysisCross-asset correlation analysis including rolling correlation, hierarchical clustering, tail dependence, and regime-dependent correlation
| 1 | # Correlation Analysis |
| 2 | |
| 3 | Cross-asset correlation analysis for diversification assessment, risk management, pairs trading signal generation, and portfolio construction. |
| 4 | |
| 5 | ## Why Correlation Matters |
| 6 | |
| 7 | Correlation measures how assets move together. In crypto markets this is critical for: |
| 8 | |
| 9 | - **Diversification**: holding correlated assets provides no diversification benefit — you are effectively holding one concentrated position |
| 10 | - **Risk management**: portfolio risk depends on the correlation structure, not just individual asset volatility |
| 11 | - **Pairs trading**: highly correlated assets that temporarily diverge create mean-reversion opportunities |
| 12 | - **Portfolio construction**: optimal allocation requires accurate correlation estimates |
| 13 | - **Crash protection**: understanding tail dependence reveals whether assets crash together |
| 14 | |
| 15 | ## Correlation Methods |
| 16 | |
| 17 | ### Pearson Correlation |
| 18 | |
| 19 | Linear correlation assuming normality. Most common but least robust for crypto. |
| 20 | |
| 21 | ```python |
| 22 | import pandas as pd |
| 23 | import numpy as np |
| 24 | |
| 25 | # Always compute on returns, never on prices |
| 26 | returns_a = prices_a.pct_change().dropna() |
| 27 | returns_b = prices_b.pct_change().dropna() |
| 28 | |
| 29 | pearson_corr = returns_a.corr(returns_b) # default is Pearson |
| 30 | ``` |
| 31 | |
| 32 | - **Range**: -1 (perfect inverse) to +1 (perfect co-movement) |
| 33 | - **Assumes**: linear relationship, normally distributed returns, no outliers |
| 34 | - **Limitation**: crypto returns are heavy-tailed — Pearson underestimates extreme co-movement |
| 35 | |
| 36 | ### Spearman Rank Correlation |
| 37 | |
| 38 | Converts values to ranks, then computes Pearson on ranks. Captures monotonic (not just linear) relationships. |
| 39 | |
| 40 | ```python |
| 41 | spearman_corr = returns_a.corr(returns_b, method='spearman') |
| 42 | ``` |
| 43 | |
| 44 | - More robust to outliers and non-linear relationships |
| 45 | - Better for crypto due to heavy-tailed return distributions |
| 46 | - Slightly lower power than Pearson when normality holds |
| 47 | |
| 48 | ### Kendall Tau Correlation |
| 49 | |
| 50 | Counts concordant vs discordant pairs. Most robust to outliers. |
| 51 | |
| 52 | ```python |
| 53 | kendall_corr = returns_a.corr(returns_b, method='kendall') |
| 54 | ``` |
| 55 | |
| 56 | - Most robust to outliers of the three methods |
| 57 | - Computationally slower on large datasets |
| 58 | - Best for small samples or heavily skewed data |
| 59 | |
| 60 | ## Rolling Correlation |
| 61 | |
| 62 | Static correlation hides regime changes. Rolling correlation reveals how relationships evolve. |
| 63 | |
| 64 | ### Window-Based Rolling Correlation |
| 65 | |
| 66 | ```python |
| 67 | # Rolling Pearson correlation |
| 68 | rolling_corr = returns_a.rolling(window=60).corr(returns_b) |
| 69 | |
| 70 | # Multiple windows for different time horizons |
| 71 | windows = { |
| 72 | 'short': 20, # ~1 month of trading days |
| 73 | 'medium': 60, # ~3 months |
| 74 | 'long': 120, # ~6 months |
| 75 | } |
| 76 | for label, w in windows.items(): |
| 77 | df[f'corr_{label}'] = returns_a.rolling(w).corr(returns_b) |
| 78 | ``` |
| 79 | |
| 80 | ### EWMA Correlation |
| 81 | |
| 82 | Exponentially weighted — more responsive to recent changes. |
| 83 | |
| 84 | ```python |
| 85 | def ewma_correlation(x: pd.Series, y: pd.Series, span: int = 60) -> pd.Series: |
| 86 | """Compute EWMA correlation between two return series.""" |
| 87 | cov_xy = x.mul(y).ewm(span=span).mean() - x.ewm(span=span).mean() * y.ewm(span=span).mean() |
| 88 | std_x = x.ewm(span=span).std() |
| 89 | std_y = y.ewm(span=span).std() |
| 90 | return cov_xy / (std_x * std_y) |
| 91 | ``` |
| 92 | |
| 93 | ### Typical Windows |
| 94 | |
| 95 | | Window | Days | Use Case | |
| 96 | |--------|------|----------| |
| 97 | | Short | 20 | Tactical trading, pairs entry/exit | |
| 98 | | Medium | 60 | Strategy allocation, regime detection | |
| 99 | | Long | 120 | Portfolio construction, strategic allocation | |
| 100 | |
| 101 | ## Correlation Matrix Analysis |
| 102 | |
| 103 | ### Computing the Full Matrix |
| 104 | |
| 105 | ```python |
| 106 | # Build return matrix for multiple assets |
| 107 | returns = pd.DataFrame({ |
| 108 | 'BTC': btc_returns, |
| 109 | 'ETH': eth_returns, |
| 110 | 'SOL': sol_returns, |
| 111 | 'AVAX': avax_returns, |
| 112 | }) |
| 113 | |
| 114 | # Correlation matrix (Pearson) |
| 115 | corr_matrix = returns.corr() |
| 116 | |
| 117 | # Spearman (better for crypto) |
| 118 | spearman_matrix = returns.corr(method='spearman') |
| 119 | ``` |
| 120 | |
| 121 | ### Eigenvalue Decomposition |
| 122 | |
| 123 | Decompose the correlation matrix to identify driving factors. |
| 124 | |
| 125 | ```python |
| 126 | eigenvalues, eigenvectors = np.linalg.eigh(corr_matrix.values) |
| 127 | |
| 128 | # Sort descending |
| 129 | idx = eigenvalues.argsort()[::-1] |
| 130 | eigenvalues = eigenvalues[idx] |
| 131 | eigenvectors = eigenvectors[:, idx] |
| 132 | |
| 133 | # First eigenvalue = market factor (explains most variance) |
| 134 | # Subsequent eigenvalues = sector/style factors |
| 135 | market_factor_pct = eigenvalues[0] / eigenvalues.sum() * 100 |
| 136 | ``` |
| 137 | |
| 138 | - **First eigenvector**: the market factor — when this dominates (>60% variance), everything moves together |
| 139 | - **Subsequent eigenvectors**: sector or style factors |
| 140 | - **Small eigenvalues**: noise / idiosyncratic risk |
| 141 | |
| 142 | ### Minimum Variance Portfolio |
| 143 | |
| 144 | ```python |
| 145 | from numpy.linalg import inv |
| 146 | |
| 147 | cov_matrix = returns.cov() |
| 148 | ones = np.ones(len(cov_matrix)) |
| 149 | inv_cov = inv(cov_matrix.values) |
| 150 | |
| 151 | # Minimum variance weights |
| 152 | weights = inv_cov @ ones / (ones @ inv_cov @ ones) |
| 153 | ``` |
| 154 | |
| 155 | ## Hierarchical Clustering |
| 156 | |
| 157 | Group assets by correlation similarity to identify natural clusters. |
| 158 | |
| 159 | ```python |
| 160 | from scipy.cluste |