Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

ETF Relative Strength and Alpha Attribution

ETF relative strength ranks funds by 20-session return versus SPY. See what alpha attribution does to the intercept once the benchmark actually fits.

ETF relative strength ranks a small basket of funds by how far each one's recent return sits above or below a benchmark, then holds whichever names finish on top. The rule this post takes apart is one line long: every 20 sessions, rank the universe by 20-session return minus SPY over the identical window, then hold the top two names in overlapping sleeves. The attribution is where a strategy like that gets tested, and in one published run of exactly this design the intercept that would have been reported as alpha came out at 0.38% a year once the regression included the exposures the rule was already buying.

How the ETF relative strength rule works

Relative strength is a comparison, not a return. A fund up 3% over 20 sessions while the benchmark is up 5% has negative relative strength. A fund down 1% while the benchmark is down 4% has positive relative strength. The number the ranking sorts on is excess return: the fund's percentage change over a fixed window, minus the benchmark's percentage change over that same window, measured session for session.

Two choices sit on top of that. The lookback sets how much history the ranking sees, with 20 sessions being roughly a calendar month of trading. The holding period sets how long a winner stays in the book. When the two are equal, the entire portfolio turns over on one date a month and the whole result hangs on which date you happened to start. Overlapping sleeves remove that dependence: run several copies of the same rule staggered by a few sessions, size each at a fraction of capital, and the blended book holds a rolling mix rather than a single start-date bet.

The panel below runs the ranking step on a nine-fund universe of broad-market, sector, international and metals ETFs, using the last 21 daily closes so the return spans exactly 20 sessions.

Query20-session return versus SPY across a nine-fund ETF universe
The exact SQL behind every number
WITH bounds AS
(
    SELECT
        min(d) AS first_day,
        max(d) AS last_day
    FROM
    (
        SELECT date AS d
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'SPY'
          AND date >= today() - 120
        GROUP BY d
        ORDER BY d DESC
        LIMIT 21
    )
)
SELECT
    sleeve.ticker                            AS etf,
    sleeve.ret_pct                           AS return_pct,
    round(sleeve.ret_pct - bench.ret_pct, 2) AS excess_vs_spy_pct
FROM
(
    SELECT
        ticker,
        round((argMax(toFloat64(close), date) / argMin(toFloat64(close), date) - 1) * 100, 2) AS ret_pct
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('QQQ', 'IWM', 'XLK', 'XLE', 'XLF', 'XLV', 'XLU', 'GLD', 'EFA')
      AND date >= (SELECT first_day FROM bounds)
      AND date <= (SELECT last_day FROM bounds)
    GROUP BY ticker
) AS sleeve
CROSS JOIN
(
    SELECT
        round((argMax(toFloat64(close), date) / argMin(toFloat64(close), date) - 1) * 100, 2) AS ret_pct
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= (SELECT first_day FROM bounds)
      AND date <= (SELECT last_day FROM bounds)
) AS bench
ORDER BY excess_vs_spy_pct DESC
Run this yourself

At the top of the ranking, GLD returned 8.69% over those 20 sessions, 6.34 points against SPY. At the bottom, XLU sat at -5.81 points. Each row carries the whole idea in two bars: what the fund did, and what is left once SPY's move over the identical window comes out. The distance between the two bars is the same number in all 9 rows, since every fund is measured against one benchmark over one window.

What alpha attribution actually measures

Alpha attribution is a regression. Line the strategy's return series up against one or more benchmark return series and fit a straight line through the scatter. The slope on each benchmark is an exposure: how much of the strategy moves one for one with that thing. The intercept is what remains after those exposures are paid for, and that leftover is what gets called alpha. R squared is the third number, the share of the strategy's variation the benchmark series account for, running from 0% (the benchmarks explain nothing) to 100% (the strategy is the benchmarks plus a constant).

Regress a rotation rule against a single broad-market series and it usually looks original. In the published attribution of a rule of this shape, open-sourced in August 2026 and run on deterministic synthetic prices, that single-benchmark fit produced an R squared of 14.0%. Six-sevenths of the strategy's variation sat unaccounted for, which reads on the page as originality. Swapping in proxies for the exposures the rule was actually holding, a momentum tilt and the sector concentration a two-name book carries, lifted R squared to 63.6% and pulled the intercept to 0.38% a year. Same return series and same code. A different comparison set. The strategy had been renting well-known exposures.

That study generates its own prices from a fixed seed, so a reader with no data vendor can reproduce every figure it prints. It is also only days old at the time of writing, and a project that young changes weekly. Pin the exact commit or tagged release you ran before quoting a number from it: attribution figures move whenever the factor proxies or the sample window change. Our note on reproducible backtest tooling covers the same habit from the other direction.

The lesson survives without the factor-model vocabulary. The benchmark you regress against determines how much alpha you appear to have, so choose it before you look at the intercept and write down why it is the right comparison. A comparison set picked after seeing the result measures the choice of comparison.

How much of an ETF is already the benchmark

You do not need a full factor model to see the problem coming. Take one fund's daily returns, line them up against SPY's daily returns on the same sessions, and square the correlation. That gives the share of the fund's day-to-day variation a single broad-market benchmark already accounts for.

QueryShare of each fund's daily variation explained by SPY, trailing two years
The exact SQL behind every number
WITH daily AS
(
    SELECT
        ticker,
        date,
        toFloat64(close) AS px,
        lagInFrame(toFloat64(close)) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'QQQ', 'IWM', 'XLK', 'XLE', 'XLF', 'XLV', 'XLU', 'GLD', 'EFA')
      AND date >= today() - 760
)
SELECT
    sleeve.ticker                                                AS etf,
    round(pow(corr(sleeve.fund_ret, bench.spy_ret), 2) * 100, 1) AS r_squared_vs_spy_pct
FROM
(
    SELECT ticker, date, px / prev_px - 1 AS fund_ret
    FROM daily
    WHERE prev_px > 0
      AND ticker != 'SPY'
) AS sleeve
INNER JOIN
(
    SELECT date, px / prev_px - 1 AS spy_ret
    FROM daily
    WHERE prev_px > 0
      AND ticker = 'SPY'
) AS bench USING (date)
GROUP BY sleeve.ticker
ORDER BY r_squared_vs_spy_pct DESC
Run this yourself

Over the trailing two years, QQQ carried the highest single-benchmark R squared in this universe at 90.3%, and GLD the lowest at 3.1%. A sleeve assembled from the high end of that chart is close to a restatement of the benchmark under a different weighting, and any intercept measured against something other than the benchmark will quietly absorb the difference. The low end is where a rotation can add a return stream the broad market does not already contain, and it is also where a two-name book stops resembling a diversified portfolio.

How often the leading fund changes

A ranking rule only has something to sort when the funds disagree. The panel below measures each fund's calendar-month return against SPY's over the same month, then keeps the strongest and the weakest sleeve for every month.

QueryStrongest and weakest sleeve versus SPY, by calendar month
The exact SQL behind every number
WITH monthly AS
(
    SELECT
        toStartOfMonth(date)           AS m,
        ticker,
        argMin(toFloat64(close), date) AS first_close,
        argMax(toFloat64(close), date) AS last_close
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'QQQ', 'IWM', 'XLK', 'XLE', 'XLF', 'XLV', 'XLU', 'GLD', 'EFA')
      AND date >= toStartOfMonth(today() - 400)
      AND date <  toStartOfMonth(today())
    GROUP BY m, ticker
)
SELECT
    formatDateTime(x.m, '%Y-%m')   AS month,
    argMax(x.ticker, x.excess_pct) AS leading_etf,
    round(max(x.excess_pct), 2)    AS leader_excess_pct,
    round(min(x.excess_pct), 2)    AS laggard_excess_pct
FROM
(
    SELECT
        sleeve.m      AS m,
        sleeve.ticker AS ticker,
        (sleeve.last_close / sleeve.first_close - 1) * 100
          - (bench.spy_last / bench.spy_first - 1) * 100 AS excess_pct
    FROM monthly AS sleeve
    INNER JOIN
    (
        SELECT
            m,
            first_close AS spy_first,
            last_close  AS spy_last
        FROM monthly
        WHERE ticker = 'SPY'
    ) AS bench USING (m)
    WHERE sleeve.ticker != 'SPY'
) AS x
GROUP BY x.m
ORDER BY x.m
Run this yourself

Across 13 complete months, the series opens in 2025-07 with XLK in front. In the most recent complete month, 2026-07, the strongest sleeve was XLE at 12.59 points against SPY, while the weakest measured -5.7. The distance between the two lines is the dispersion a ranking rule feeds on. Months where the lines pinch together are months where the sort is close to arbitrary, and a rule that trades on every ranking pays turnover in all of them. Month boundaries deserve their own care here; how monthly returns are measured walks through the conventions.

Is two-name concentration the real risk?

Volatility is the plainest version of that question. This panel annualizes each fund's daily return variation over the same two years and carries SPY as the reference bar, alongside the worst single session each one printed.

QueryAnnualized volatility and worst single session, trailing two years
The exact SQL behind every number
WITH daily AS
(
    SELECT
        ticker,
        date,
        toFloat64(close) AS px,
        lagInFrame(toFloat64(close)) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'QQQ', 'IWM', 'XLK', 'XLE', 'XLF', 'XLV', 'XLU', 'GLD', 'EFA')
      AND date >= today() - 760
)
SELECT
    ticker                                                   AS etf,
    round(stddevSamp(px / prev_px - 1) * sqrt(252) * 100, 1) AS annual_vol_pct,
    round(min(px / prev_px - 1) * 100, 2)                    AS worst_day_pct
FROM daily
WHERE prev_px > 0
GROUP BY ticker
ORDER BY annual_vol_pct DESC
Run this yourself

XLK sat at the wide end at 27.7% annualized, with a worst single session of -6.82%, and XLU at the narrow end at 15.8%. A rule holding two names at a time draws from this range without averaging across it, and the deeper drawdowns that come with the wide end land in an attribution as exposure rather than as skill. The low volatility anomaly note covers the long-run record at the narrow end of the same chart.

Four checks for your own rotation backtest

  1. Same-day signal leakage. If the 20-session ranking is computed through a session's close and the fill is booked at that same close, the test trades on information it did not hold at the moment of the trade. Rank on data through session t, fill at session t plus one, and compare the two versions: the gap between them is the size of the leak. Look-ahead bias shows how large that gap gets.
  2. Survivorship in the fund list. A universe assembled from funds that exist today excludes every fund that closed or merged along the way, and closures cluster in the products that performed worst. Survivorship bias applies to an ETF list exactly as it does to a stock list.
  3. Benchmark chosen in advance. Write the comparison set down before the first regression, including proxies for the exposures you expect the rule to hold. The intercept means something only against a benchmark you committed to in advance.
  4. Concentration measured directly. Run the same ranking holding the top two names, then the top four, then an equal weight of the whole universe. If most of the reported edge lives in the two-name version, concentration is the position, and the ranking is a way of choosing which concentration to take.

FAQ

What is ETF relative strength?

Relative strength is a fund's return over a fixed lookback minus a benchmark's return over that same window. A ranking sorted on it puts the funds that outpaced the benchmark on top, whether or not their raw returns were positive.

What is alpha attribution?

It is a regression of a strategy's returns on one or more benchmark return series. The slopes measure the exposures the strategy carries, and the intercept is the return left over once those exposures are accounted for. That intercept is the number usually reported as alpha.

Does a high R squared mean a strategy is bad?

No. A high R squared means the benchmarks account for most of the strategy's variation, which is a statement about overlap rather than about quality. What changes is the intercept: exposures the regression can now see stop being counted as originality.

Why hold overlapping sleeves instead of one portfolio?

A single portfolio rebalanced every 20 sessions makes the result depend on the start date. Running several staggered copies of the same rule averages that choice away and spreads turnover across the month rather than concentrating it on one date.


Every panel here ships with the SQL that produced it. Open one, swap in your own fund list, and re-run the ranking on the Strasmore terminal to watch the ordering move.

#etfs#momentum#quant#factor models#attribution