Risk-free rate in the Sharpe ratio
The risk-free rate in the Sharpe ratio moves over time. How to match a bill yield to your return frequency, and what one fixed rate costs the answer.
The risk-free rate in the Sharpe ratio is the return you could have collected over the same window without taking the risk you are grading. It is a series, one reading per period, quoted in the currency of the returns. Match its frequency to the return frequency and subtract it period by period. Annualize once, at the end.
Skipping that procedure is where most published Sharpe ratios go wrong. Lifting one number off the top of a twenty-year return history, usually whatever a Treasury bill pays this week, charges 2009 the same hurdle as 2024. Our guide to the Sharpe ratio covers what the ratio measures. This page covers the input almost nobody documents.
The risk-free rate is not a constant
A Sharpe ratio measured over twenty years spans a rate history. The panel below takes the three-month Treasury bill and collapses each calendar year into an average and the range around it.
The exact SQL behind every number
SELECT
toString(toYear(date)) AS year,
round(avg(toFloat64(yield_3_month)), 2) AS avg_bill_pct,
round(min(toFloat64(yield_3_month)), 2) AS low_bill_pct,
round(max(toFloat64(yield_3_month)), 2) AS high_bill_pct
FROM global_markets.treasury_yields
WHERE date >= '2005-01-01'
AND yield_3_month IS NOT NULL
GROUP BY year
ORDER BY yearThe yearly average ran 3.22% in 2005 and 3.74% in 2026, across 22 calendar years. The path between those two readings is the part that matters. The line drops to the floor, stays there for most of a decade, then climbs back. The low and high columns show how far the rate travelled inside single years, which is why even a one-year Sharpe ratio wants a matched series rather than one quote.
Which risk-free rate belongs in the Sharpe ratio
Four properties decide the pick.
- Short maturity. The rate has to be near-certain over one measurement period. A three-month bill qualifies for monthly work. A ten-year note does not, since its price moves during the month you are measuring.
- The return's own currency. A euro-denominated strategy is graded against a euro bill.
- The return's own frequency. Bill yields are quoted as annual rates, and an annual quote has to be converted down before it touches a monthly return.
- Comparable return definitions on both sides. A bill's yield is its entire return, so a strategy return that excludes dividends is not measured on the same footing. Price return versus total return covers that gap.
The conversion in point three has two conventions in circulation. Simple division splits the annual quote by twelve. Compounding takes the twelfth root of one plus the annual rate. On a hypothetical 5% bill those give 0.4167% and 0.4074% for the month, about a basis point of difference that repeats in every month of the sample. The panels here compound. If bill yields interest you as a holding rather than as a hurdle, dividend yield versus Treasury yields and where to park idle cash cover that ground.
How to build a monthly excess return series
The construction is mechanical: take the last close of each month, form the month's return, convert that month's bill quote to a monthly rate, then subtract. What comes out is an excess return series, one number per month. How monthly returns are measured covers the return leg in detail.
The exact SQL behind every number
WITH
monthly_px AS
(
SELECT
toStartOfMonth(date) AS m,
argMax(toFloat64(close), date) AS month_close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2019-12-01'
AND date < '2025-01-01'
GROUP BY m
),
prior_px AS
(
SELECT
addMonths(m, 1) AS m,
month_close AS prev_close
FROM monthly_px
),
monthly_rf AS
(
SELECT
toStartOfMonth(date) AS m,
pow(1 + avg(toFloat64(yield_3_month)) / 100, 1.0 / 12) - 1 AS rf_month
FROM global_markets.treasury_yields
WHERE date >= '2019-12-01'
AND date < '2025-01-01'
AND yield_3_month IS NOT NULL
GROUP BY m
)
SELECT
toString(cur.m) AS month,
round(100 * (cur.month_close / prv.prev_close - 1), 2) AS spy_ret_pct,
round(100 * rf.rf_month, 3) AS rf_pct,
round(100 * (cur.month_close / prv.prev_close - 1 - rf.rf_month), 2) AS excess_pct
FROM monthly_px AS cur
INNER JOIN prior_px AS prv ON prv.m = cur.m
INNER JOIN monthly_rf AS rf ON rf.m = cur.m
ORDER BY cur.mThose are SPY closes, so the return column is a price return with dividends left out. The risk-free column opens at 0.128% for the first month in view and closes at 0.359%. Next to equity months that swing several percent, the risk-free strip looks like rounding. Across 60 months of subtraction it is the whole difference between two Sharpe ratios that claim to measure the same thing.
Subtract first, then annualize
Two orders of operation are available once the series are lined up, and they disagree. Excess first: subtract the monthly rate from the monthly return, then annualize the excess series. Annualize first: compound the returns into an annual figure, compound the bill into an annual figure, then subtract one from the other.
The exact SQL behind every number
WITH
monthly_px AS
(
SELECT
toStartOfMonth(date) AS m,
argMax(toFloat64(close), date) AS month_close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2004-12-01'
AND date < '2025-01-01'
GROUP BY m
),
prior_px AS
(
SELECT
addMonths(m, 1) AS m,
month_close AS prev_close
FROM monthly_px
),
monthly_rf AS
(
SELECT
toStartOfMonth(date) AS m,
pow(1 + avg(toFloat64(yield_3_month)) / 100, 1.0 / 12) - 1 AS rf_month
FROM global_markets.treasury_yields
WHERE date >= '2004-12-01'
AND date < '2025-01-01'
AND yield_3_month IS NOT NULL
GROUP BY m
),
excess AS
(
SELECT
cur.m AS m,
cur.month_close / prv.prev_close - 1 AS ret,
rf.rf_month AS rf_month,
cur.month_close / prv.prev_close - 1 - rf.rf_month AS exc
FROM monthly_px AS cur
INNER JOIN prior_px AS prv ON prv.m = cur.m
INNER JOIN monthly_rf AS rf ON rf.m = cur.m
)
SELECT
concat(toString(intDiv(count(), 12)), '-year') AS horizon,
round(100 * (exp(12 * avg(log(1 + exc))) - 1), 2) AS excess_first_pct,
round(100 * ((exp(12 * avg(log(1 + ret))) - 1)
- (exp(12 * avg(log(1 + rf_month))) - 1)), 2) AS annualize_first_pct,
round(100 * ((exp(12 * avg(log(1 + ret))) - 1)
- (exp(12 * avg(log(1 + rf_month))) - 1)
- (exp(12 * avg(log(1 + exc))) - 1)), 3) AS gap_pct
FROM excess
CROSS JOIN (SELECT arrayJoin([1, 3, 5, 10, 20]) AS years) AS hz
WHERE m >= subtractYears(toDate('2025-01-01'), years)
GROUP BY years
ORDER BY yearsOver the 20-year window the excess-first answer is 6.49% a year against 6.59% for the other order, a gap of 0.099 percentage points. At the 1-year window the same two computations sit 0.814 points apart. The annualize-first figure prints above the excess-first figure in every window here. What separates them is a cross term: compounding a return series into an annual number includes earning the bill rate on top of the risk premium itself, and subtracting an annualized bill afterwards never takes that piece back out. The cross term scales with the product of the two annualized numbers, so it is widest when bills pay well.
Annualizing has a second fork in it. Arithmetic annualization multiplies the average monthly excess by twelve. Geometric annualization compounds: the constant annual rate that reproduces the cumulative result. Take a hypothetical two-period run of +50% followed by -33.3%, which ends exactly where it started. The arithmetic average is +8.35% per period and the geometric average is zero. The panels here compound. One footnote worth keeping: under arithmetic annualization, twelve times the mean return minus twelve times the mean bill rate is algebraically the same as twelve times the mean excess. Under that convention the order of subtraction leaves the numerator alone. The denominator still moves, since the standard deviation of excess returns parts company with the standard deviation of raw returns once the rate itself is travelling.
What one fixed rate costs the answer
The common shortcut is to take the current bill yield and subtract it from every period in the history. The panel below runs the Sharpe ratio both ways across five windows that end on the same date: once against the matched monthly series, once against the bill yield in force at the end of the window.
The exact SQL behind every number
WITH
monthly_px AS
(
SELECT
toStartOfMonth(date) AS m,
argMax(toFloat64(close), date) AS month_close
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= '2004-12-01'
AND date < '2025-01-01'
GROUP BY m
),
prior_px AS
(
SELECT
addMonths(m, 1) AS m,
month_close AS prev_close
FROM monthly_px
),
monthly_rf AS
(
SELECT
toStartOfMonth(date) AS m,
pow(1 + avg(toFloat64(yield_3_month)) / 100, 1.0 / 12) - 1 AS rf_month
FROM global_markets.treasury_yields
WHERE date >= '2004-12-01'
AND date < '2025-01-01'
AND yield_3_month IS NOT NULL
GROUP BY m
),
excess AS
(
SELECT
cur.m AS m,
cur.month_close / prv.prev_close - 1 AS ret,
rf.rf_month AS rf_month
FROM monthly_px AS cur
INNER JOIN prior_px AS prv ON prv.m = cur.m
INNER JOIN monthly_rf AS rf ON rf.m = cur.m
)
SELECT
concat(toString(intDiv(count(), 12)), '-year') AS horizon,
round(sqrt(12) * avg(ret - rf_month) / stddevSamp(ret - rf_month), 2) AS sharpe_matched_rf,
round(sqrt(12) * avg(ret - rf_fixed) / stddevSamp(ret - rf_fixed), 2) AS sharpe_fixed_rf,
round(abs(sqrt(12) * avg(ret - rf_month) / stddevSamp(ret - rf_month)
- sqrt(12) * avg(ret - rf_fixed) / stddevSamp(ret - rf_fixed)), 2) AS abs_gap
FROM excess
CROSS JOIN
(
SELECT pow(1 + avg(toFloat64(yield_3_month)) / 100, 1.0 / 12) - 1 AS rf_fixed
FROM global_markets.treasury_yields
WHERE date >= '2024-12-01'
AND date < '2025-01-01'
AND yield_3_month IS NOT NULL
) AS fixed_rate
CROSS JOIN (SELECT arrayJoin([1, 3, 5, 10, 20]) AS years) AS hz
WHERE m >= subtractYears(toDate('2025-01-01'), years)
GROUP BY years
ORDER BY yearsAt the 1-year window the two readings land 0.07 apart. Over twelve months the fixed rate has little room to drift from the window's own average. At the 20-year window they sit 0.18 apart: 0.49 on the matched series against 0.32 on the fixed rate. The error tracks the distance between the single rate you picked and the average rate that actually prevailed, and that distance has more room to open up as the window lengthens. Anything downstream inherits it, and volatility targeting and position sizing runs on inputs of exactly this kind.
The currency trap
A strategy that earns in one currency and is graded against another currency's bill produces a number that means little in either. Compute the excess return inside one currency: euro returns against a euro bill, yen returns against a yen bill. Hedged strategies carry a second trap. A currency hedge prices off the forward, and forward points sit close to the interest rate differential between the two currencies, which already places part of the domestic rate inside a hedged foreign return. Subtracting the foreign bill on top of that charges the same rate twice. Name the currency next to the rate and the ambiguity goes away.
Publish the rate alongside the ratio
A Sharpe ratio quoted without its risk-free source and frequency cannot be checked by anyone. Two desks can compute 0.8 and 1.1 from the identical return stream and both be right, working from different rate series and different conversion conventions. Four items travel with the number: the instrument the rate came from, its frequency, the conversion convention, and the exact window. That is the difference between a number a reader can audit and a number a reader has to take on faith.
Data notes and reproducibility
The rate series in every panel is the three-month Treasury bill from the daily Treasury yield curve, averaged within each calendar month, converted to a monthly rate as the twelfth root of one plus the annual quote. Returns are SPY month-end closes, price return only, with dividends excluded. The ratios use the sample standard deviation of monthly excess returns and the square root of twelve for annualization. The last two panels use windows ending on a common date, and the fixed-rate column takes the average bill quote in that window's final month. Every figure in the prose above comes from the panel next to it.
FAQ
Which risk-free rate should I use in the Sharpe ratio?
A short government bill in the same currency as the returns, converted to the same frequency as the returns. For monthly USD returns that means the one-month or three-month Treasury bill. Longer maturities carry price risk over the measurement period, which disqualifies them as a risk-free leg.
Can I subtract today's Treasury bill yield from a long return history?
It is the most common mistake in published Sharpe ratios. Over the 20-year window above, a fixed end-of-window rate and the matched monthly series produce readings 0.18 apart on the very same returns.
Do I annualize the risk-free rate before subtracting it?
No. Convert the annual bill quote down to the return's frequency, subtract period by period, then annualize the excess series once at the end. Annualizing each leg separately and subtracting afterwards leaves a cross term sitting in the answer.
How much does the risk-free rate move a Sharpe ratio?
Roughly the change in the rate divided by the annualized volatility. On a hypothetical strategy running 15% annualized volatility, a 4% difference in the assumed rate moves the ratio by about 0.27, which is the distance between an ordinary track record and a celebrated one.
What is the difference between arithmetic and geometric annualization?
Arithmetic multiplies the average period excess by the number of periods in a year. Geometric compounds the periods into the constant rate that reproduces the cumulative result. Per period the geometric figure sits at or below the arithmetic one, and the distance between them widens with volatility.
Every panel here carries the SQL that produced it, so the rate series and the window are both visible. To run the same construction on another ticker or another stretch of history, ask for it in plain English on the Strasmore terminal.