← All posts

Which Sectors Work When Bonds Rally? Rate-Sensitive Rotation in Python

Rank sector ETFs by their average return during top-quintile Treasury bond rally windows.

What's the question?

Bond rallies often signal falling interest-rate expectations, weaker growth expectations, or both. Equity sectors do not respond equally. Utilities and healthcare may benefit from lower discount rates and defensive demand. Energy may lag if the bond rally reflects weaker nominal growth. Technology can benefit from lower long-duration discount rates, but its response also depends on risk appetite. At the index level, an analysis of the stock-bond correlation looks for a break in how stocks and long Treasuries move together.

The practical question is which sectors have recently performed best during bond-rally windows. This is a sector-rotation question. Sector rotation means shifting exposure across sectors as macro conditions change.

The approach

The bond proxy is TLT, the long-duration Treasury ETF. The equity universe is XLK, XLF, XLI, XLV, XLP, XLU, and XLE, with SPY included as the market reference. Built from SEC EDGAR public filings and market data, the analysis uses three years of daily prices.

  1. Calculate 21-day returns for TLT and each sector ETF
  2. Define bond-rally windows as the top 20% of 21-day TLT returns
  3. Measure each sector's average 21-day return during those windows
  4. Compare those returns with each sector's average return across all windows
  5. Report each sector's latest 63-day momentum and daily beta to TLT

This method avoids choosing a single rate-cut date. The data defines the strongest bond-rally regimes.

Code

import xfinlink as xfl
import pandas as pd

xfl.set_api_key("YOUR_API_KEY")  # free at https://xfinlink.com/signup

sectors = ["XLK", "XLF", "XLI", "XLV", "XLP", "XLU", "XLE"]
tickers = ["TLT", "SPY"] + sectors

df = xfl.prices(tickers, period="3y", fields=["adj_close"])
prices = df.pivot_table(index="date", columns="ticker", values="adj_close")[tickers].dropna()
window_returns = prices.pct_change(21, fill_method=None).dropna()

threshold = window_returns["TLT"].quantile(0.80)
rallies = window_returns["TLT"] >= threshold

result = window_returns.loc[rallies, sectors].mean().sort_values(ascending=False)
print(result)

Full script with formatting and visualisation: rate-sensitive-sector-rotation-python.py

Output

Sector ETF returns during top-quintile TLT bond rally windows
=== Rate-Sensitive Sector Rotation Screen ===
Sample: 2023-06-07 to 2026-06-04 (751 trading days)
Bond-rally definition: top 20% of 21-day TLT returns
TLT rally threshold:                 2.5%
Number of bond-rally windows:     147
Best sector in bond rallies:      XLU (   3.5%)
Worst sector in bond rallies:     XLE (  -0.7%)

Sector ranking during bond-rally windows:
XLU  rally_return=   3.5%  all_windows=  -0.5%  latest_63d_momentum=  -6.3%  beta_to_TLT= 0.35
XLV  rally_return=   2.8%  all_windows=   0.4%  latest_63d_momentum=  -1.2%  beta_to_TLT= 0.21
XLK  rally_return=   2.7%  all_windows=   1.0%  latest_63d_momentum=  37.8%  beta_to_TLT= 0.10
XLF  rally_return=   2.6%  all_windows=   1.4%  latest_63d_momentum=   1.9%  beta_to_TLT= 0.12
XLI  rally_return=   2.4%  all_windows=   1.6%  latest_63d_momentum=   2.4%  beta_to_TLT= 0.16
XLP  rally_return=   2.0%  all_windows=   0.4%  latest_63d_momentum=  -3.9%  beta_to_TLT= 0.20
XLE  rally_return=  -0.7%  all_windows=  -0.2%  latest_63d_momentum=   4.0%  beta_to_TLT=-0.09

What this tells us

The strongest bond-rally sector is XLU, with an average 21-day return of 3.5% during top-quintile TLT rallies. XLV follows at 2.8%, and XLK is close at 2.7%. Energy is the outlier. XLE averages -0.7% during the same windows and has a negative daily beta to TLT.

The result separates rate sensitivity from simple momentum. XLK has very strong latest 63-day momentum at 37.8%, but its beta to TLT is only 0.10. XLU has poor latest momentum at -6.3%, but it has the highest bond-rally return and the highest TLT beta in the group.

That means a bond-rally rotation is not the same as buying the strongest current sector. It is a macro-conditional screen.

So what?

When long-duration bonds rally, sector allocation should be judged against the reason for the rally. If the move reflects falling discount rates without a severe growth shock, utilities, healthcare, and technology can all work. If the move reflects weaker nominal growth, energy has recently been the least attractive sector in this framework.

For tactical allocation, the screen supports a two-step rule: first identify whether TLT is in a strong 21-day rally, then rank sectors by historical behavior in those windows. The output turns a macro narrative into a repeatable rotation rule.

Built with xfinlink — free financial data API for Python.