Stock Correlation Analysis Skill

Analyze stock correlations to find related companies and trading pairs.

How to use it

Claude Code
  1. Run the line below. It pulls the whole folder into ~/.claude/skills/stock-correlation.
  2. Describe your job in plain words. Claude Code follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit himself65/finance-skills/plugins/market-analysis/skills/stock-correlation#main ~/.claude/skills/stock-correlation

For one project only, change the path to .claude/skills/stock-correlation.

Claude (web or desktop app)
  1. On this page open ⋯ → Download .md.
  2. Save it as SKILL.md in a folder, zip the folder, then Customize → Skills → + → Create skill → Upload a skill.
  3. Pick the file and Save. Claude shows the name and description and runs a security scan.
  4. Check the skill is switched on.
  5. Start a new chat and describe your job in plain words. The AI follows the skill from there.
ChatGPT or another app
  1. ChatGPT: make a Project and paste it into Instructions.
  2. Neither? Paste it at the top of a new chat — it works for that chat.
Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Source of Stock Correlation Analysis Skill

Show the full text368 lines
namedescription
stock-correlation> Analyze stock correlations to find related companies and trading pairs. Use when the user asks about correlated stocks, related companies, sector peers, trading pairs, or how two or more stocks move together. Triggers: "what correlates with NVDA", "find stocks related to AMD", correlation between AAPL and MSFT", "what moves with", "sector peers", pair trading", "correlated stocks", "when NVDA drops what else drops", stocks that move together", "beta to", "relative performance", supply chain partners", "correlation matrix", "co-movement", related tickers", "sympathy plays", "semiconductor peers", hedging pair", "realized correlation", "rolling correlation", or any request about stocks that move in tandem or inversely. Also triggers for well-known pairs like AMD/NVDA, GOOGL/AVGO, LITE/COHR. If only one ticker is provided, infer the user wants correlated peers.

Stock Correlation Analysis Skill

Finds and analyzes correlated stocks using historical price data from Yahoo Finance via yfinance. Routes to specialized sub-skills based on user intent.

Important: This is for research and educational purposes only. Not financial advice. yfinance is not affiliated with Yahoo, Inc.


Step 1: Ensure Dependencies Are Available

Current environment status:

!`python3 -c "exec('try:\n import yfinance, pandas, numpy\n print(f\'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}\')\nexcept Exception:\n print(\'DEPS_MISSING\')')"`

If DEPS_MISSING, install required packages before running any code:

import subprocess, sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance", "pandas", "numpy"])

If all dependencies are already installed, skip the install step and proceed directly.


Step 2: Route to the Correct Sub-Skill

Classify the user's request and jump to the matching sub-skill section below.

User Request Route To Examples
Single ticker, wants to find related stocks Sub-Skill A: Co-movement Discovery "what correlates with NVDA", "find stocks related to AMD", "sympathy plays for TSLA"
Two or more specific tickers, wants relationship details Sub-Skill B: Return Correlation "correlation between AMD and NVDA", "how do LITE and COHR move together", "compare AAPL vs MSFT"
Group of tickers, wants structure/grouping Sub-Skill C: Sector Clustering "correlation matrix for FAANG", "cluster these semiconductor stocks", "sector peers for AMD"
Wants time-varying or conditional correlation Sub-Skill D: Realized Correlation "rolling correlation AMD NVDA", "when NVDA drops what else drops", "how has correlation changed"

If ambiguous, default to Sub-Skill A (Co-movement Discovery) for single tickers, or Sub-Skill B (Return Correlation) for two tickers.

Defaults for all sub-skills
Parameter Default
Lookback period 1y (1 year)
Data interval 1d (daily)
Correlation method Pearson
Minimum correlation threshold 0.60
Number of results Top 10
Return type Daily log returns
Rolling window 60 trading days

Sub-Skill A: Co-movement Discovery

Goal: Given a single ticker, find stocks that move with it.

A1: Build the peer universe

You need 15-30 candidates. Do not use hardcoded ticker lists — build the universe dynamically at runtime. See references/sector_universes.md for the full implementation. The approach:

  1. Screen same-industry stocks using yf.screen() + yf.EquityQuery to find stocks in the same industry as the target
  2. Broaden to sector if the industry screen returns fewer than 10 peers
  3. Add thematic/adjacent industries — read the target's longBusinessSummary and screen 1-2 related industries (e.g., a semiconductor company → also screen semiconductor equipment)
  4. Combine, deduplicate, remove target ticker
A2: Compute correlations
import yfinance as yf
import pandas as pd
import numpy as np

def discover_comovement(target_ticker, peer_tickers, period="1y"):
    all_tickers = [target_ticker] + [t for t in peer_tickers if t != target_ticker]
    data = yf.download(all_tickers, period=period, auto_adjust=True, progress=False)

    # Extract close prices — yf.download returns MultiIndex (Price, Ticker) columns
    closes = data["Close"].dropna(axis=1, thresh=max(60, len(data) // 2))

    # Log returns
    returns = np.log(closes / closes.shift(1)).dropna()
    corr_series = returns.corr()[target_ticker].drop(target_ticker, errors="ignore")

    # Rank by absolute correlation
    ranked = corr_series.abs().sort_values(ascending=False)

    result = pd.DataFrame({
        "Ticker": ranked.index,
        "Correlation": [round(corr_series[t], 4) for t in ranked.index],
    })
    return result, returns
A3: Present results

Show a ranked table with company names and sectors (fetch via yf.Ticker(t).info.get("shortName")):

Rank Ticker Company Correlation Why linked
1 AMD Advanced Micro Devices 0.82 Same industry — GPU/CPU
2 AVGO Broadcom 0.78 AI infrastructure peer

Include:

  • Top 10 positively correlated stocks
  • Any notable negatively correlated stocks (potential hedges)
  • Brief explanation of why each might be linked (sector, supply chain, customer overlap)

Sub-Skill B: Return Correlation

Goal: Deep-dive into the relationship between two (or a few) specific tickers.

B1: Download and compute
import yfinance as yf
import pandas as pd
import numpy as np

def return_correlation(ticker_a, ticker_b, period="1y"):
    data = yf.download([ticker_a, ticker_b], period=period, auto_adjust=True, progress=False)
    closes = data["Close"][[ticker_a, ticker_b]].dropna()

    returns = np.log(closes / closes.shift(1)).dropna()
    corr = returns[ticker_a].corr(returns[ticker_b])

    # Beta: how much does B move per unit move of A
    cov_matrix = returns.cov()
    beta = cov_matrix.loc[ticker_b, ticker_a] / cov_matrix.loc[ticker_a, ticker_a]

    # R-squared
    r_squared = corr ** 2

    # Rolling 60-day correlation for stability
    rolling_corr = returns[ticker_a].rolling(60).corr(returns[ticker_b])

    # Spread (log price ratio) for mean-reversion
    spread = np.log(closes[ticker_a] / closes[ticker_b])
    spread_z = (spread - spread.mean()) / spread.std()

    return {
        "correlation": round(corr, 4),
        "beta": round(beta, 4),
        "r_squared": round(r_squared, 4),
        "rolling_corr_mean": round(rolling_corr.mean(), 4),
        "rolling_corr_std": round(rolling_corr.std(), 4),
        "rolling_corr_min": round(rolling_corr.min(), 4),
        "rolling_corr_max": round(rolling_corr.max(), 4),
        "spread_z_current": round(spread_z.iloc[-1], 4),
        "observations": len(returns),
    }
B2: Present results

Show a summary card:

Metric Value
Pearson Correlation 0.82
Beta (B vs A) 1.15
R-squared 0.67
Rolling Corr (60d avg) 0.80
Rolling Corr Range [0.55, 0.94]
Rolling Corr Std Dev 0.08
Spread Z-Score (current) +1.2
Observations 250

Interpretation guide:

  • Correlation > 0.80: Strong co-movement — these stocks are tightly linked
  • Correlation 0.50–0.80: Moderate — shared sector drivers but independent factors too
  • Correlation < 0.50: Weak — limited co-movement despite possible sector overlap
  • High rolling std: Unstable relationship — correlation varies significantly over time
  • Spread Z > |2|: Unusual divergence from historical relationship

Sub-Skill C: Sector Clustering

Goal: Given a group of tickers, show the full correlation structure and identify clusters.

C1: Build the correlation matrix
import yfinance as yf
import pandas as pd
import numpy as np

def sector_clustering(tickers, period="1y"):
    data = yf.download(tickers, period=period, auto_adjust=True, progress=False)

    # yf.download returns MultiIndex (Price, Ticker) columns
    closes = data["Close"].dropna(axis=1, thresh=max(60, len(data) // 2))
    returns = np.log(closes / closes.shift(1)).dropna()
    corr_matrix = returns.corr()

    # Hierarchical clustering order
    from scipy.cluster.hierarchy import linkage, leaves_list
    from scipy.spatial.distance import squareform

    dist_matrix = 1 - corr_matrix.abs()
    np.fill_diagonal(dist_matrix.values, 0)
    condensed = squareform(dist_matrix)
    linkage_matrix = linkage(condensed, method="ward")
    order = leaves_list(linkage_matrix)
    ordered_tickers = [corr_matrix.columns[i] for i in order]

    # Reorder matrix
    clustered = corr_matrix.loc[ordered_tickers, ordered_tickers]

    return clustered, returns

Note: if scipy is not available, fall back to sorting by average correlation instead of hierarchical clustering.

C2: Present results
  1. Full correlation matrix — formatted as a table. For more than 8 tickers, show as a heatmap description or highlight only the strongest/weakest pairs.

  2. Identified clusters — group tickers that have high intra-group correlation:

    • Cluster 1: [NVDA, AMD, AVGO] — avg intra-correlation 0.82
    • Cluster 2: [AAPL, MSFT] — avg intra-correlation 0.75
  3. Outliers — tickers with low average correlation to the group (potential diversifiers).

  4. Strongest pairs — top 5 highest-correlation pairs in the matrix.

  5. Weakest pairs — top 5 lowest/negative-correlation pairs (hedging candidates).


Sub-Skill D: Realized Correlation

Goal: Show how correlation changes over time and under different market conditions.

D1: Rolling correlation
import yfinance as yf
import pandas as pd
import numpy as np

def realized_correlation(ticker_a, ticker_b, period="2y", windows=[20, 60, 120]):
    data = yf.download([ticker_a, ticker_b], period=period, auto_adjust=True, progress=False)
    closes = data["Close"][[ticker_a, ticker_b]].dropna()

    returns = np.log(closes / closes.shift(1)).dropna()

    rolling = {}
    for w in windows:
        rolling[f"{w}d"] = returns[ticker_a].rolling(w).corr(returns[ticker_b])

    return rolling, returns
D2: Regime-conditional correlation
def regime_correlation(returns, ticker_a, ticker_b, condition_ticker=None):
    """Compare correlation across up/down/volatile regimes."""
    if condition_ticker is None:
        condition_ticker = ticker_a

    ret = returns[condition_ticker]

    regimes = {
        "All Days": pd.Series(True, index=returns.index),
        "Up Days (target > 0)": ret > 0,
        "Down Days (target < 0)": ret < 0,
        "High Vol (top 25%)": ret.abs() > ret.abs().quantile(0.75),
        "Low Vol (bottom 25%)": ret.abs() < ret.abs().quantile(0.25),
        "Large Drawdown (< -2%)": ret < -0.02,
    }

    results = {}
    for name, mask in regimes.items():
        subset = returns[mask]
        if len(subset) >= 20:
            results[name] = {
                "correlation": round(subset[ticker_a].corr(subset[ticker_b]), 4),
                "days": int(mask.sum()),
            }

    return results
D3: Present results
  1. Rolling correlation summary table:
Window Current Mean Min Max Std
20-day 0.88 0.76 0.32 0.95 0.12
60-day 0.82 0.78 0.55 0.92 0.08
120-day 0.80 0.79 0.68 0.88 0.05
  1. Regime correlation table:
Regime Correlation Days
All Days 0.82 250
Up Days 0.75 132
Down Days 0.87 118
High Vol (top 25%) 0.90 63
Large Drawdown (< -2%) 0.93 28
  1. Key insight: Highlight whether correlation increases during sell-offs (very common — "correlations go to 1 in a crisis"). This is critical for risk management.

  2. Trend: Is correlation trending higher or lower recently vs. its historical average?


Step 3: Respond to the User

After running the appropriate sub-skill, present results clearly:

Always include
  • The lookback period and data interval used
  • The number of observations (trading days)
  • Any tickers dropped due to insufficient data
Always caveat
  • Correlation is not causation — co-movement does not imply a causal link
  • Past correlation does not guarantee future correlation — regimes shift
  • Short lookback windows produce noisy estimates; longer windows smooth but may miss regime changes
Practical applications (mention when relevant)
  • Sympathy plays: Stocks likely to follow a peer's earnings/news move
  • Pair trading: High-correlation pairs where the spread has diverged from its mean
  • Portfolio diversification: Finding low-correlation assets to reduce risk
  • Hedging: Identifying inversely correlated instruments
  • Sector rotation: Understanding which sectors move together
  • Risk management: Correlation spikes during stress — diversification may fail when needed most

Important: Never recommend specific trades. Present data and let the user draw conclusions.


Reference Files

  • references/sector_universes.md — Dynamic peer universe construction using yfinance Screener API

Read the reference file when you need to build a peer universe for a given ticker.

1---
2name: stock-correlation
3description: >
4 Analyze stock correlations to find related companies and trading pairs.
5 Use when the user asks about correlated stocks, related companies, sector peers,
6 trading pairs, or how two or more stocks move together.
7 Triggers: "what correlates with NVDA", "find stocks related to AMD",
8 "correlation between AAPL and MSFT", "what moves with", "sector peers",
9 "pair trading", "correlated stocks", "when NVDA drops what else drops",
10 "stocks that move together", "beta to", "relative performance",
11 "supply chain partners", "correlation matrix", "co-movement",
12 "related tickers", "sympathy plays", "semiconductor peers",
13 "hedging pair", "realized correlation", "rolling correlation",
14 or any request about stocks that move in tandem or inversely.
15 Also triggers for well-known pairs like AMD/NVDA, GOOGL/AVGO, LITE/COHR.
16 If only one ticker is provided, infer the user wants correlated peers.
17---
18 
19# Stock Correlation Analysis Skill
20 
21Finds and analyzes correlated stocks using historical price data from Yahoo Finance via [yfinance](https://github.com/ranaroussi/yfinance). Routes to specialized sub-skills based on user intent.
22 
23**Important**: This is for research and educational purposes only. Not financial advice. yfinance is not affiliated with Yahoo, Inc.
24 
25---
26 
27## Step 1: Ensure Dependencies Are Available
28 
29**Current environment status:**
30 
31```
32!`python3 -c "exec('try:\n import yfinance, pandas, numpy\n print(f\'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}\')\nexcept Exception:\n print(\'DEPS_MISSING\')')"`
33```
34 
35If `DEPS_MISSING`, install required packages before running any code:
36 
37```python
38import subprocess, sys
39subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance", "pandas", "numpy"])
40```
41 
42If all dependencies are already installed, skip the install step and proceed directly.
43 
44---
45 
46## Step 2: Route to the Correct Sub-Skill
47 
48Classify the user's request and jump to the matching sub-skill section below.
49 
50| User Request | Route To | Examples |
51|---|---|---|
52| Single ticker, wants to find related stocks | **Sub-Skill A: Co-movement Discovery** | "what correlates with NVDA", "find stocks related to AMD", "sympathy plays for TSLA" |
53| Two or more specific tickers, wants relationship details | **Sub-Skill B: Return Correlation** | "correlation between AMD and NVDA", "how do LITE and COHR move together", "compare AAPL vs MSFT" |
54| Group of tickers, wants structure/grouping | **Sub-Skill C: Sector Clustering** | "correlation matrix for FAANG", "cluster these semiconductor stocks", "sector peers for AMD" |
55| Wants time-varying or conditional correlation | **Sub-Skill D: Realized Correlation** | "rolling correlation AMD NVDA", "when NVDA drops what else drops", "how has correlation changed" |
56 
57If ambiguous, default to **Sub-Skill A** (Co-movement Discovery) for single tickers, or **Sub-Skill B** (Return Correlation) for two tickers.
58 
59### Defaults for all sub-skills
60 
61| Parameter | Default |
62|---|---|
63| Lookback period | `1y` (1 year) |
64| Data interval | `1d` (daily) |
65| Correlation method | Pearson |
66| Minimum correlation threshold | 0.60 |
67| Number of results | Top 10 |
68| Return type | Daily log returns |
69| Rolling window | 60 trading days |
70 
71---
72 
73## Sub-Skill A: Co-movement Discovery
74 
75**Goal**: Given a single ticker, find stocks that move with it.
76 
77### A1: Build the peer universe
78 
79You need 15-30 candidates. **Do not use hardcoded ticker lists** — build the universe dynamically at runtime. See `references/sector_universes.md` for the full implementation. The approach:
80 
811. **Screen same-industry stocks** using `yf.screen()` + `yf.EquityQuery` to find stocks in the same industry as the target
822. **Broaden to sector** if the industry screen returns fewer than 10 peers
833. **Add thematic/adjacent industries** — read the target's `longBusinessSummary` and screen 1-2 related industries (e.g., a semiconductor company → also screen semiconductor equipment)
844. **Combine, deduplicate, remove target ticker**
85 
86### A2: Compute correlations
87 
88```python
89import yfinance as yf
90import pandas as pd
91import numpy as np
92 
93def discover_comovement(target_ticker, peer_tickers, period="1y"):
94 all_tickers = [target_ticker] + [t for t in peer_tickers if t != target_ticker]
95 data = yf.download(all_tickers, period=period, auto_adjust=True, progress=False)
96 
97 # Extract close prices — yf.download returns MultiIndex (Price, Ticker) columns
98 closes = data["Close"].dropna(axis=1, thresh=max(60, len(data) // 2))
99 
100 # Log returns
101 returns = np.log(closes / closes.shift(1)).dropna()
102 corr_series = returns.corr()[target_ticker].drop(target_ticker, errors="ignore")
103 
104 # Rank by absolute correlation
105 ranked = corr_series.abs().sort_values(ascending=False)
106 
107 result = pd.DataFrame({
108 "Ticker": ranked.index,
109 "Correlation": [round(corr_series[t], 4) for t in ranked.index],
110 })
111 return result, returns
112```
113 
114### A3: Present results
115 
116Show a ranked table with company names and sectors (fetch via `yf.Ticker(t).info.get("shortName")`):
117 
118| Rank | Ticker | Company | Correlation | Why linked |
119|---|---|---|---|---|
120| 1 | AMD | Advanced Micro Devices | 0.82 | Same industry — GPU/CPU |
121| 2 | AVGO | Broadcom | 0.78 | AI infrastructure peer |
122 
123Include:
124- Top 10 positively correlated stocks
125- Any notable negatively correlated stocks (potential hedges)
126- Brief explanation of **why** each might be linked (sector, supply chain, customer overlap)
127 
128---
129 
130## Sub-Skill B: Return Correlation
131 
132**Goal**: Deep-dive into the relationship between two (or a few) specific tickers.
133 
134### B1: Download and compute
135 
136```python
137import yfinance as yf
138import pandas as pd
139import numpy as np
140 
141def return_correlation(ticker_a, ticker_b, period="1y"):
142 data = yf.download([ticker_a, ticker_b], period=period, auto_adjust=True, progress=False)
143 closes = data["Close"][[ticker_a, ticker_b]].dropna()
144 
145 returns = np.log(closes / closes.shift(1)).dropna()
146 corr = returns[ticker_a].corr(returns[ticker_b])
147 
148 # Beta: how much does B move per unit move of A
149 cov_matrix = returns.cov()
150 beta = cov_matrix.loc[ticker_b, ticker_a] / cov_matrix.loc[ticker_a, ticker_a]
151 
152 # R-squared
153 r_squared = corr ** 2
154 
155 # Rolling 60-day correlation for stability
156 rolling_corr = returns[ticker_a].rolling(60).corr(returns[ticker_b])
157 
158 # Spread (log price ratio) for mean-reversion
159 spread = np.log(closes[ticker_a] / closes[ticker_b])
160 spread_z = (spread - spread.mean()) / spread.std()
161 
162 return {
163 "correlation": round(corr, 4),
164 "beta": round(beta, 4),
165 "r_squared": round(r_squared, 4),
166 "rolling_corr_mean": round(rolling_corr.mean(), 4),
167 "rolling_corr_std": round(rolling_corr.std(), 4),
168 "rolling_corr_min": round(rolling_corr.min(), 4),
169 "rolling_corr_max": round(rolling_corr.max(), 4),
170 "spread_z_current": round(spread_z.iloc[-1], 4),
171 "observations": len(returns),
172 }
173```
174 
175### B2: Present results
176 
177Show a summary card:
178 
179| Metric | Value |
180|---|---|
181| Pearson Correlation | 0.82 |
182| Beta (B vs A) | 1.15 |
183| R-squared | 0.67 |
184| Rolling Corr (60d avg) | 0.80 |
185| Rolling Corr Range | [0.55, 0.94] |
186| Rolling Corr Std Dev | 0.08 |
187| Spread Z-Score (current) | +1.2 |
188| Observations | 250 |
189 
190Interpretation guide:
191- **Correlation > 0.80**: Strong co-movement — these stocks are tightly linked
192- **Correlation 0.50–0.80**: Moderate — shared sector drivers but independent factors too
193- **Correlation < 0.50**: Weak — limited co-movement despite possible sector overlap
194- **High rolling std**: Unstable relationship — correlation varies significantly over time
195- **Spread Z > |2|**: Unusual divergence from historical relationship
196 
197---
198 
199## Sub-Skill C: Sector Clustering
200 
201**Goal**: Given a group of tickers, show the full correlation structure and identify clusters.
202 
203### C1: Build the correlation matrix
204 
205```python
206import yfinance as yf
207import pandas as pd
208import numpy as np
209 
210def sector_clustering(tickers, period="1y"):
211 data = yf.download(tickers, period=period, auto_adjust=True, progress=False)
212 
213 # yf.download returns MultiIndex (Price, Ticker) columns
214 closes = data["Close"].dropna(axis=1, thresh=max(60, len(data) // 2))
215 returns = np.log(closes / closes.shift(1)).dropna()
216 corr_matrix = returns.corr()
217 
218 # Hierarchical clustering order
219 from scipy.cluster.hierarchy import linkage, leaves_list
220 from scipy.spatial.distance import squareform
221 
222 dist_matrix = 1 - corr_matrix.abs()
223 np.fill_diagonal(dist_matrix.values, 0)
224 condensed = squareform(dist_matrix)
225 linkage_matrix = linkage(condensed, method="ward")
226 order = leaves_list(linkage_matrix)
227 ordered_tickers = [corr_matrix.columns[i] for i in order]
228 
229 # Reorder matrix
230 clustered = corr_matrix.loc[ordered_tickers, ordered_tickers]
231 
232 return clustered, returns
233```
234 
235Note: if `scipy` is not available, fall back to sorting by average correlation instead of hierarchical clustering.
236 
237### C2: Present results
238 
2391. **Full correlation matrix** — formatted as a table. For more than 8 tickers, show as a heatmap description or highlight only the strongest/weakest pairs.
240 
2412. **Identified clusters** — group tickers that have high intra-group correlation:
242 - Cluster 1: [NVDA, AMD, AVGO] — avg intra-correlation 0.82
243 - Cluster 2: [AAPL, MSFT] — avg intra-correlation 0.75
244 
2453. **Outliers** — tickers with low average correlation to the group (potential diversifiers).
246 
2474. **Strongest pairs** — top 5 highest-correlation pairs in the matrix.
248 
2495. **Weakest pairs** — top 5 lowest/negative-correlation pairs (hedging candidates).
250 
251---
252 
253## Sub-Skill D: Realized Correlation
254 
255**Goal**: Show how correlation changes over time and under different market conditions.
256 
257### D1: Rolling correlation
258 
259```python
260import yfinance as yf
261import pandas as pd
262import numpy as np
263 
264def realized_correlation(ticker_a, ticker_b, period="2y", windows=[20, 60, 120]):
265 data = yf.download([ticker_a, ticker_b], period=period, auto_adjust=True, progress=False)
266 closes = data["Close"][[ticker_a, ticker_b]].dropna()
267 
268 returns = np.log(closes / closes.shift(1)).dropna()
269 
270 rolling = {}
271 for w in windows:
272 rolling[f"{w}d"] = returns[ticker_a].rolling(w).corr(returns[ticker_b])
273 
274 return rolling, returns
275```
276 
277### D2: Regime-conditional correlation
278 
279```python
280def regime_correlation(returns, ticker_a, ticker_b, condition_ticker=None):
281 """Compare correlation across up/down/volatile regimes."""
282 if condition_ticker is None:
283 condition_ticker = ticker_a
284 
285 ret = returns[condition_ticker]
286 
287 regimes = {
288 "All Days": pd.Series(True, index=returns.index),
289 "Up Days (target > 0)": ret > 0,
290 "Down Days (target < 0)": ret < 0,
291 "High Vol (top 25%)": ret.abs() > ret.abs().quantile(0.75),
292 "Low Vol (bottom 25%)": ret.abs() < ret.abs().quantile(0.25),
293 "Large Drawdown (< -2%)": ret < -0.02,
294 }
295 
296 results = {}
297 for name, mask in regimes.items():
298 subset = returns[mask]
299 if len(subset) >= 20:
300 results[name] = {
301 "correlation": round(subset[ticker_a].corr(subset[ticker_b]), 4),
302 "days": int(mask.sum()),
303 }
304 
305 return results
306```
307 
308### D3: Present results
309 
3101. **Rolling correlation summary table**:
311 
312| Window | Current | Mean | Min | Max | Std |
313|---|---|---|---|---|---|
314| 20-day | 0.88 | 0.76 | 0.32 | 0.95 | 0.12 |
315| 60-day | 0.82 | 0.78 | 0.55 | 0.92 | 0.08 |
316| 120-day | 0.80 | 0.79 | 0.68 | 0.88 | 0.05 |
317 
3182. **Regime correlation table**:
319 
320| Regime | Correlation | Days |
321|---|---|---|
322| All Days | 0.82 | 250 |
323| Up Days | 0.75 | 132 |
324| Down Days | 0.87 | 118 |
325| High Vol (top 25%) | 0.90 | 63 |
326| Large Drawdown (< -2%) | 0.93 | 28 |
327 
3283. **Key insight**: Highlight whether correlation **increases during sell-offs** (very common — "correlations go to 1 in a crisis"). This is critical for risk management.
329 
3304. **Trend**: Is correlation trending higher or lower recently vs. its historical average?
331 
332---
333 
334## Step 3: Respond to the User
335 
336After running the appropriate sub-skill, present results clearly:
337 
338### Always include
339 
340- The **lookback period** and **data interval** used
341- The **number of observations** (trading days)
342- Any tickers **dropped due to insufficient data**
343 
344### Always caveat
345 
346- **Correlation is not causation** — co-movement does not imply a causal link
347- **Past correlation does not guarantee future correlation** — regimes shift
348- **Short lookback windows** produce noisy estimates; longer windows smooth but may miss regime changes
349 
350### Practical applications (mention when relevant)
351 
352- **Sympathy plays**: Stocks likely to follow a peer's earnings/news move
353- **Pair trading**: High-correlation pairs where the spread has diverged from its mean
354- **Portfolio diversification**: Finding low-correlation assets to reduce risk
355- **Hedging**: Identifying inversely correlated instruments
356- **Sector rotation**: Understanding which sectors move together
357- **Risk management**: Correlation spikes during stress — diversification may fail when needed most
358 
359**Important**: Never recommend specific trades. Present data and let the user draw conclusions.
360 
361---
362 
363## Reference Files
364 
365- `references/sector_universes.md` — Dynamic peer universe construction using yfinance Screener API
366 
367Read the reference file when you need to build a peer universe for a given ticker.
368 

Discussion