Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

How ETF relative strength and alpha attribution dey work

See how ETF relative strength rank funds against SPY using twenty session returns. Learn how alpha attribution change the intercept when the benchmark fit well.

How the ETF relative strength rule dey work

Relative strength na comparison, e no be return. If fund go up 3% for 20 sessions but benchmark go up 5%, the relative strength na negative. If fund go down 1% but benchmark go down 4%, the relative strength na positive. The number wey dem dey use rank na excess return: the percentage change of the fund over one fixed window, minus the percentage change of the benchmark over the same window, wey dem measure session by session.

Two things dey matter here. The lookback period show how much history the ranking dey look, and 20 sessions dey roughly equal one calendar month of trading. The holding period show how long winner go stay inside the book. When the two period equal, the whole portfolio dey turn over one time every month and the result go depend on the date wey you start. Overlapping sleeves dey remove that wahala: you go run many copies of the same rule wey dey stagger by few sessions, you give each one small portion of capital, and the blended book go hold rolling mix instead of one single bet wey start for one date.

The panel wey dey down here run the ranking step on nine-fund universe wey get broad-market, sector, international and metals ETFs, wey use the last 21 daily closes so the return go cover exactly 20 sessions.

Query20-session return wey dem compare wit SPY across 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

For the top of the ranking, GLD return 8.69% over those 20 sessions, 6.34 points against SPY. For the bottom, XLU sit at -5.81 points. Each row carry the whole idea for two bars: wetin the fund do, and wetin remain after you remove SPY move over the same window. The distance between the two bars na the same number for all 9 rows, because dem measure every fund against one benchmark over one window.

Wetin alpha attribution truly dey measure

Alpha attribution na regression. You go line the strategy return series up against one or more benchmark return series and fit straight line through the scatter. The slope on each benchmark na exposure: e show how much the strategy dey move one-for-one with that thing. The intercept na wetin remain after you don pay for those exposures, and that leftover na wetin dem dey call alpha. R squared na the third number, e show the share of strategy variation wey the benchmark series explain, e dey start from 0% (benchmarks no explain anything) go reach 100% (strategy na the benchmarks plus constant).

If you regress rotation rule against one single broad-market series, e go look like say e original. For the published attribution of rule wey get this shape, wey dem open-source for August 2026 and run on deterministic synthetic prices, that single-benchmark fit produce R squared of 14.0%. Six-sevenths of the strategy variation no get explanation, wey make am look like originality for paper. When dem swap in proxies for the exposures wey the rule dey hold, like momentum tilt and sector concentration wey two-name book get, R squared rise go 63.6% and the intercept drop go 0.38% a year. Same return series and same code. Just different comparison set. The strategy bin dey rent well-known exposures.

That study dey generate im own prices from fixed seed, so reader wey no get data vendor fit reproduce every figure wey e print. E still dey fresh, and project wey young like that dey change every week. Make sure you pin the exact commit or tagged release wey you run before you quote any number from am: attribution figures dey move anytime the factor proxies or sample window change. Our note on reproducible backtest tooling cover the same habit from the other side.

The lesson dey clear even without factor-model vocabulary. The benchmark wey you regress against go determine how much alpha you get, so choose am before you look at the intercept and write down why e be the right comparison. Comparison set wey you pick after you don see the result na just measurement of the choice of comparison.

How much of an ETF na already the benchmark

You no need full factor model to see the problem. Take one fund daily returns, line them up against SPY daily returns for the same sessions, and square the correlation. That one go give you the share of the fund day-to-day variation wey single broad-market benchmark don already account for.

QueryHow much SPY dey explain each fund daily variation, for di past 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 past two years, QQQ carry the highest single-benchmark R squared for this universe at 90.3%, and GLD get the lowest at 3.1%. Sleeve wey you build from the high end of that chart dey close to restatement of the benchmark under different weighting, and any intercept wey you measure against something wey no be the benchmark go quietly absorb the difference. The low end na where rotation fit add return stream wey broad market no get, and na also where two-name book stop to resemble diversified portfolio.

How often the leading fund dey change

Ranking rule get something to sort only when the funds no agree. The panel wey dey down here measure each fund calendar-month return against SPY over the same month, then e keep the strongest and weakest sleeve for every month.

QueryDi strongest and weakest sleeve against SPY, per 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 open for 2025-07 with XLK for front. For the most recent complete month, 2026-07, the strongest sleeve na XLE at 12.59 points against SPY, while the weakest measure -5.7. The distance between the two lines na the dispersion wey ranking rule dey feed on. Months wey the lines pinch together na months wey the sort dey almost arbitrary, and rule wey dey trade on every ranking go pay turnover for all of them. Month boundaries deserve special care here; how monthly returns are measured explain the conventions.

Two-name concentration na the real risk?

Volatility na the plainest version of that question. This panel annualize each fund daily return variation over the same two years and carry SPY as reference bar, plus the worst single session wey each one print.

QueryAnnualized volatility and di worst single session, for di past 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 sit for the wide end at 27.7% annualized, with worst single session of -6.82%, and XLU for the narrow end at 15.8%. Rule wey hold two names at time dey draw from this range without averaging across am, and the deeper drawdowns wey come with the wide end go land for attribution as exposure instead of skill. The low volatility anomaly note cover the long-run record for the narrow end of the same chart.

Four checks for your own rotation backtest

  1. Same-day signal leakage. If you calculate 20-session ranking through session close and you book the fill for that same close, the test dey trade on information wey e no get for 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 na the size of the leak. Look-ahead bias show how large that gap fit be.
  2. Survivorship for the fund list. Universe wey you build from funds wey dey exist today exclude every fund wey close or merge along the way, and closures dey cluster for products wey perform worst. Survivorship bias apply to ETF list exactly as e apply to stock list.
  3. Benchmark wey you choose in advance. Write the comparison set down before the first regression, including proxies for the exposures wey you expect the rule to hold. The intercept get meaning only against benchmark wey you commit to in advance.
  4. Concentration wey you measure directly. Run the same ranking holding the top two names, then the top four, then equal weight of the whole universe. If most of the reported edge live for the two-name version, concentration na the position, and the ranking na way to choose which concentration to take.

FAQ

Wetin be ETF relative strength?

Relative strength na fund return over fixed lookback minus benchmark return over the same window. Ranking wey dem sort on top am go put the funds wey pass the benchmark for top, whether their raw returns positive or not.

Wetin be alpha attribution?

E be regression of strategy returns on one or more benchmark return series. The slopes measure the exposures wey the strategy carry, and the intercept na the return wey remain after you don account for those exposures. That intercept na the number wey dem usually report as alpha.

High R squared mean say strategy bad?

No. High R squared mean say the benchmarks account for most of the strategy variation, wey be statement about overlap instead of quality. Wetin dey change na the intercept: exposures wey the regression fit see now go stop to count as originality.

Why hold overlapping sleeves instead of one portfolio?

Single portfolio wey you rebalance every 20 sessions go make the result depend on the start date. Running many staggered copies of the same rule go average that choice away and spread turnover across the month instead of concentrating am on one date.


Every panel here come with the SQL wey produce am. 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