Bootstrapping a SOFR curve
Dec 31, 2024
brooklyn
A yield curve is a useful tool for understanding the state of the market and is often used for valuing financial contracts. There is no one yield curve, there are treasury yield curves (at least one for every country), corporate yield curves (one for each issuing entity), and yield curves tied to particular rates, such as SOFR or €STR.
What is a yield curve?
A yield curve is a term structure of interest rates. A term structure is simply a time-dependent value. In this case, there are different SOFR rates, depending on the duration of the deposit. In practice, there aren’t liquid quotes for SOFR deposits beyond the overnight rate, but, based on the rates of other types of SOFR-linked derivatives, we should be able to determine what the no-arbitrage rate would be.
Let’s assume that the SOFR par curve as of 30 December 2024 looks like this:
Instrument | Maturity | Rate / Price |
---|---|---|
Overnight (ON) Deposit | 2024-12-31 | 5.00% |
SR3Z4 Future | 2025-03-19 | 94.75 |
SR3H5 Future | 2025-06-18 | 94.50 |
SR3M5 Future | 2025-09-17 | 94.25 |
SR3U5 Future | 2025-12-17 | 94.00 |
1Y Swap | 2025-12-30 | 5.80% |
2Y Swap | 2026-12-30 | 5.90% |
3Y Swap | 2027-12-30 | 6.00% |
4Y Swap | 2028-12-30 | 6.10% |
5Y Swap | 2029-12-30 | 6.20% |
6Y Swap | 2030-12-30 | 6.25% |
7Y Swap | 2031-12-30 | 6.30% |
8Y Swap | 2032-12-30 | 6.35% |
9Y Swap | 2033-12-30 | 6.40% |
10Y Swap | 2034-12-30 | 6.45% |
Note that I have ignored payment delays and business day conventions, for simplicity
Perhaps you’ve seen a similar chart for treasury yields.
A note on nomenclature: This post will frequently refer to the SOFR curve. In most cases, this refers to the zero-coupon yield curve, which will be defined later. In some cases, it may refer to the par yield curve, which are the quotes observed in the market for liquid, SOFR-based derivatives.
Why do we want a SOFR yield curve?
So the first question you might be asking yourself is – why do we want this particular yield curve? The US Treasury curve is certainly indicative of the US economy, generally. And what is SOFR?
What is SOFR?
SOFR stands for the Secured Overnight Financing Rate. It is the rate that a firm would get for loaning a US Treasury bond on an collateralized, overnight basis. These loans are called repurchase agreements (repos). One party, the seller, sells a treasury bond, worth, say, USD \(1M\), and the other party, the buyer, sends cash. The seller promises to give back the 1M in cash, plus some amount of interest, tomorrow. The seller sells the bond but agrees to buy it back in one day. It may, or may not, surprise you to learn that this is one of the most liquid markets on earth. There is well over a trillion USDs traded every day in repos 1.
Still, why would we care to know what the collateral interest rate is on repos? What is it useful for?
First, the fact that this market is so liquid should tell you something. Repos are how many financial institutions leverage themselves. Most financial institutions have significant quantities of US Treasuries on their balance sheets because, in part, they get favorable regulatory treatment.
Banks represent the a large proportion of the participants in this market, so their funding (and, more broadly, their collective financial condition) is closely tied to SOFR. That was actually the original goal of LIBOR – it was meant to be the rate at which banks funded themselves. Now, that’s what SOFR is.
In addition, virtually any financial institution can participate in the repo market. This includes governments, banks (including medium and small banks), hedge funds, insurance firms, mutual funds, pension funds, and corporations 2.
SOFR represents, to at least some degree, the overall financial condition of US financial markets. And, at the end of the day, that makes it important to project what the rate will be in the future.
Why do I need a yield curve?
Now that we have an understanding of the rate itself and why we need it, we should also understand why we need a yield curve. I wrote before that a yield curve is a term structure of rates. However, the example I provided referred to a particular kind of yield curve, a par yield curve. You may have noticed that there are several kinds of instruments used in the SOFR curve, so it would be helpful if we could normalize those rates. Comparing a SOFR futures price and a SOFR swap rate is usually not going to be very meaningful without normalizing them.
It would also be helpful if those rates we represented in a way that made them dimension-less. Ideally, if we could convert them to their equivalent “zero-coupon bond” rate.
What’s a zero coupon bond?
A zero-coupon bond is a very simple type of bond that is available in the market for some issuers.
The name is hopefully clear what it is – its a bond that does not pay a coupon, instead it only repays its principal amount at the maturity of the bond. When interest rates are positive, they are sold at a discount, so you might pay USD \(98\) for a bond that repays USD \(100\) in six months.
A US T-Bill is a commonly-issued type of zero-coupon bond, which is the type of bond the US Treasury uses when borrowing for maturities of one year or less.
The advantages of zero coupon bonds are three-fold:
-
Simplicity. I don’t have to do much math to figure out what the interest rate is on a zero coupon bond, because its effectively a cash deposit (Note that we will be ignoring any issuer or counterparty credit risk for this post).
-
Discounting. If we assume that implied deposit rate is the risk-free rate, then we can use that to discount projected cashflows for other bonds, derivatives, and contracts. Taking our example, if we assume US Treasury bills are risk-free, then the value of USD \(1\) in six months is USD \(0.98\). If I am due to receive some other payment in six months, after adjusting for any credit risk, I can say convert that projected payment into a present value very simply, once I have this rate.
-
Projection. I will show later that it is fairly straightforward to convert zero-coupon interest rates to forward interest rates (i.e., interest rates from one future time to another, further time). If I know what the interest rate is for today to six months from now and I know what the rate is for today to one year from now, surely I can deduce the rate from six months to one year. This feature ends up being very handy when trying to project future interest rates.
Why SOFR instead of treasuries?
The reason that we’re building SOFR curve instead of treasuries is because they are based on transactions made by many market participants. Whereas treasuries can only be issued by the US government and are, as a result, subject to all kinds of characteristics that make them less indicative of the overall condition of the US market, SOFR is based on arms-length transactions by all kinds of market participants.
What types of quotes are used to build a SOFR yield curve?
There are a few things to note about the SOFR curve. First, we have three separate types of instruments – a deposit, a future, and a swap – that typically make up the par yield curve. Ideally, you could construct the curve using only one or two instrument types. However, in practice, depending on the particular time period you’re looking at for interest rates, the most liquid type of instrument will be different. For example, if you’re looking only at the overnight rate, you would need only look at the published SOFR rate. If you’re looking for the rate for more than that, but still less than a year, you could trade a short swap, but futures are more liquid. And finally, for longer periods, the only liquid instruments are swaps.
It’s important to look at liquidity because we’re trying to figure out what the market expectation is for the interest rate. If the trading for some instrument is less liquid, the quality of that expectation is reduced.
Deposits
A deposit, in this case an overnight deposit, is the annualized rate you would receive on a cash deposit at the SOFR interest rate. This rate is published every date by the New York Federal Reserve for the prior day. 3 The average person cannot deposit money with the Federal Reserve, but banks often pay this rate in interest to their depositors. Note that the SOFR rate is calculated on an \( actual / 360 \) accrual basis for annualization. We will discuss what an accrual basis is later.
The future value on a cash deposit is calculated according to the following formula:
$$ \begin{equation} \text{Future value} = \text{Principal} \times \big(1 + SOFR_i \times \frac{days_{i,i+1}}{360} \big) \end{equation} $$
where,
$$ \begin{align} Principal &= \text{the initial investment} \nonumber \\ SOFR_i &= \text{the SOFR rate for the i-th date} \nonumber \\ days_{i,i+1} &= \text{the number of days applicable for the i-th SOFR rate} \nonumber \\ \end{align} $$
Note that SOFR is not published on holidays and weekends, the depositor will continue to accrue interest at the rate applicable for the previous business day. As a result, \(days_{i,i+1}\) may be more than one day.
Futures
A future is an exchange-traded contract that will settle at a price that aligns with a payoff specified by the contract 4. In this case, the way that SOFR interest rate futures work, the rate can be calculated as \((100 - \text{price}) \div 100 \). This contract is based on the observed daily compounded SOFR rate for the preceding three months. For example, SR3Z4 is based on the daily compounded SOFR rate from 18 December 2024 to 19 March 2025. In the example above, the market expects that the SOFR rate, on a simple interest basis, during that period will be \(5.25\%\).
The rate is calculated according to the following formula:
$$ \begin{equation} 100 - \text{price} = \bigg[ \prod_{i=0}^N \big[ 1 + SOFR_i \times \frac{days_{i,i+1}}{360} \big] - 1 \bigg] \times \frac{360}{D} \times 100 \end{equation} $$
where,
$$ \begin{align} N &= \text{the total number of market days in the period} \nonumber \\ SOFR_i &= \text{the SOFR rate for the i-th date} \nonumber \\ days_{i,i+1} &= \text{the number of days applicable for the i-th SOFR rate} \nonumber \\ D &= \text{the total number of calendar days in the period} \nonumber \\ \end{align} $$
For convenience, we will define \(R\) as the SOFR rate implied by the futures price, so,
$$ \begin{align} R &= \frac{(100 - \text{price})}{100} \\ R &= \bigg[ \prod_{i=0}^N \big[ 1 + SOFR_i \times \frac{days_{i,1+1}}{360} \big] - 1 \bigg] \times \frac{360}{D} \end{align} $$
Swaps
A swap is typically an over-the-counter (OTC) agreement between two parties to exchange interest on a periodic basis for an agreed-upon period of time. Because swaps are OTC agreements, the contractual terms can be negotiated by the parties, but there are conventional terms that are most common for SOFR swaps and we will use those for this post.
These are the most common conventions for SOFR swaps:
Fixed Leg | Floating Leg | |
---|---|---|
Rate | Fixed (e.g., 5.00%) | Daily Compounded SOFR |
Payment Frequency | Annually (in-arrears) | Annually (in-arrears) |
Accrual Basis | Actual / 360 | Actual / 360 |
Business Day Convention | Modified Following | Modified Following |
Each side of the swap is called a leg, so that there is a fixed leg (i.e., the leg linked to the fixed rate of interest) and a floating leg (i.e., the leg linked to the floating rate of interest). Conventionally, a payer swap is one in which we’re paying fixed interest and a receiver swap is one in which we’re receiving fixed interest.
The key for swaps is that the fixed rate is agreed-upon at the beginning so that the swap is worth USD \(0\) at the beginning. As a result, the fixed rate is the market’s expectation about the level of interest rates for the particular period that the swap is based on, at the time it is transacted.
The value of a payer swap is calculated according to the following formula:
$$ \begin{align} PV_{swap} &= PV_{float} - PV_{fixed} \\ PV_{float} &= \sum_{i=1}^N \text{Notional} \times \bigg[ \prod_{j=1}^M \big[ 1 + SOFR_j \times \frac{days_{j,j+1}}{360} \big] - 1 \bigg] \times \frac{360}{D_i} \times \frac{D_i}{360} \times df_i \\ PV_{fixed} &= \sum_{i=1}^N \text{Notional} \times r_{fixed} \times \frac{D_i}{360} \times df_i \end{align} $$
where,
$$ \begin{align} PV &= \text{the present value of the swap or relevant leg} \nonumber \\ N &= \text{the total number of payment periods in the swap} \nonumber \\ Notional &= \text{the notional quantity of currency for the swap} \nonumber \\ M &= \text{the total number of market days in the i-th payment period} \nonumber \\ SOFR_j &= \text{the SOFR rate for the j-th date} \nonumber \\ days_{j,j+1} &= \text{the number of days applicable for the SOFR rate on the j-th date} \nonumber \\ df_i &= \text{the discount factor for the i-th payment date} \nonumber \\ r_{fixed} &= \text{the annualized fixed rate of interest} \nonumber \\ D_i &= \text{the total number of calendar days in the i-th period} \nonumber \\ \end{align} $$
You may have noticed that the last two terms of \( PV_{float} \), \(\frac{360}{D_i}\) and \(\frac{D_i}{360}\), are inverses of one another. The first term annualizes the rate and the second calculates the length of the accrual period, which is used for calculating the payment due for any interest payment. Because the rate accrual basis and the floating leg accrual basis are the same, these can be elided in this case. However, in the case where the are different, perhaps because the accrual basis chosen by the parties to the transaction isn’t the same as the rate basis, you would need annualize the rate and calculate the length of the accrual period, each in accordance with the appropriate accrual basis.
So, for our example, the market expects that, for the period from 30 December 2024 to 30 December 2025, the daily compounded SOFR rate will be \(6.00\%\). As SOFR continues to be published over the course of the year and the market’s expectation changes, the value of the swap will change to reflect that the SOFR rate, perhaps, didn’t go up as was expected, or maybe went up even more. These differences will cause the swap to become an asset or a liability, depending on those changes and which side of the swap you’re on.
Concepts in financial mathematics
Understanding why we might need a zero-coupon SOFR yield curve and the types of instruments that would be used in that curve, we can begin to construct that curve.
First, on nomenclature, this post will describe how to bootstrap a zero-coupon SOFR curve. Curve construction is a broad term that can refer to several different methods to obtain zero-coupon interest rates from par interest rates.
Present value and future value
In order to value future payment, we need interest rates. When loan money in exchange for some future repayment, with interest, we are incurring some opportunity cost. If we had the cash now, we could invest the money at some interest rate. Therefore, assuming interest rates are positive, having money now is better than money later.
We are going to assume that SOFR is a risk-free rate. This is a term of art in finance and economics that refers to the rate of interest you would receive for some cash deposit that has no risk of non-payment by the entity paying the interest. As an aside, SOFR is about as risk-free as you can get – it is the rate for a loan of only one night (overnight) that is collateralized by a US treasury bond. This means that, even if the counterparty to your repo wasn’t able to pay you back, you’d receive a US treasury bond equal to the value of your loan.
Let’s look at an example,
$$ \begin{equation} FV = PV \times \big[ 1 + SOFR_i \times \frac{days_{i,i+1}}{360} \big] \\ \end{equation} $$
or,
$$ \begin{equation} PV = \frac{FV}{1 + SOFR_i \times \frac{days_{i,i+1}}{360}} \\ \end{equation} $$
Where,
$$ \begin{align} FV &= \text{Future value, the undiscounted value of some future principal payment} \nonumber \\ PV &= \text{Present value, the discounted value of some future principal payment} \nonumber \\ SOFR_i &= \text{the SOFR rate for the i-th date} \nonumber \\ days_{i,i+1} &= \text{the number of days applicable for the SOFR rate} \nonumber \\ \end{align} $$
Note that this example is only for overnight interest. If we were looking for the FV over some longer period, we’d need to consider compounding.
So, taking our assumption that SOFR is a risk-free rate, let’s look at the present value of a payment of USD \(100\) tomorrow and a SOFR rate of \(5.00\%\).
$$ \begin{align} PV &= \frac{100}{1 + 5.00 \% \times \frac{1}{360}} \nonumber \\ PV &= \frac{100}{1.00013889} \nonumber \\ PV &= 99.986113 \nonumber \\ \end{align} $$
So if we were to invest USD \(99.986113\) at the SOFR rate today, we’d receive USD \(100\) tomorrow. Given that SOFR is a risk-free rate, we can also say that we’re indifferent to these two amounts – purely from a valuation perspective – so we’re ok with either USD \(99.986113\) today or USD \(100\) tomorrow.
Discount factors and zero rates
Conventionally, the present value of USD \(1\) to be paid at some point in the future is called a discount factor, \(df\).
So,
$$ \begin{equation} PV = FV \times df \\ \end{equation} $$
and, for our SOFR overnight deposit,
$$ \begin{align} df &= \frac{1}{1 + SOFR_i \times \frac{days_{i,i+1}}{360}} \\ df &= 0.99986113 \\ \nonumber \\ PV &= 100 \times df \nonumber \\ PV &= 99.986113 \nonumber \\ \end{align} $$
A deposit can be thought of as a zero-coupon bond. You are paying some amount today, a principal amount, and receiving that principal amount plus some interest at some point in the future. So we can say that a discount factor and a zero-coupon interest rate are equivalent. One is simply a different representation of the other, with one caveat. A rate embeds within it some annualization factor, so in order to use it to discount some future payment, we need to adjust our rate. A discount factor has no such factor.
Another way to represent a discount factor is as a rate, but not as a deposit rate. Instead, a zero rate (or zero-coupon rate, \(ZR\)). This is a continuously compounded rate, which is conventionally represented on an \(actual / 365\) basis.
Specifically,
$$ \begin{equation} ZR_t = -\ln(df_t) \times \frac{365}{days_0,t} \\ \end{equation} $$
We will use discount factors for the remainder of the post, but understand that if we know that accrual basis of the rate underlying the discount factor, then we can directly convert the discount factor back to any rate.
Bootstrapping
In order to bootstrap our zero-coupon SOFR yield curve, we first start with the present value of USD \(1\) today, which is, of course, \(1\).
$$ \begin{equation} df_0 = 1 \\ \end{equation} $$
Next, the way to calculate the discount factor for a certain instrument will depend on the type of instrument.
-
For deposit rates, we can calculate the discount factor directly, because its equivalent to a zero-coupon bond.
-
For futures, we will have to use our knowledge of how to calculate forward interest rates to calculate the discount factor for the end of the futures accrual period.
-
For swaps, we will use our knowledge of already bootstrapped discount factors, forward rates, and our fixed rate to calculate the discount factor for the maturity of the swap.
Deposits
Bootstrapping deposit rates are the easiest because, as noted previously, they are equivalent to zero-coupon rates. We’ve already calculated the discount factor for our deposit rate in Equation \(9\).
Our curve begins, then,
ID | Instrument | Maturity | Par Rate / Price | Zero rate | Discount Factor |
---|---|---|---|---|---|
\(df_0\) | 2024-12-30 | 1.0 | |||
\(df_1\) | Overnight (ON) Deposit | 2024-12-31 | 5.00% | 5.0691% | 0.99986113 |
Futures
Futures are forward rates, that is, the rate from one time in the future to another, further, time. Based on the market price for SR3Z4 on 30 December 2024, the market expects that the rate over the period from 18 December 2024 to 19 March 2025 to be 5.25%.
Before we start calculating the discount factor for the future, however, we need to know how to calculate forward rates. I noted previously that, using zero-coupon rates, we should be able to know what the interest rate is from some future time to some further future time if we know the zero-coupon rate to each time. Take two rates for times 0 to 1 (\(r_{0,1}\)) and 0 to 2 (\(r_{0,2}\)), we can calculate what the forward rate (\(f_{1,2}\)) is from time 1 to 2.
$$ \begin{equation} (1 + r_{0,2} \times \frac{days_{0,2}}{360}) = (1 + r_{0,1} \times \frac{days_{0,1}}{360}) \times (1 + f_{1,2} \times \frac{days_{1,2}}{360}) \\ \end{equation} $$
Remember, $$ \begin{equation} df = \frac{1}{1 + SOFR_i \times \frac{days_{i,i+1}}{360}} \tag{11} \\ \end{equation} $$
Therefore,
$$ \begin{align} \frac{1}{df_2} &= \frac{1}{df_1} \times (1 + f_{1,2} \times \frac{days_{1,2}}{360}) \nonumber \\ (1 + f_{1,2} \times \frac{days_{1,2}}{360}) &= \frac{df_1}{df_2} \nonumber \\ f_{1,2} &= (\frac{df_1}{df_2} - 1) \times \frac{360}{days_{1,2}} \end{align} $$
We encounter two challenges with our first future. First, SR3Z4 is already accruing interest, so we will need to incorporate those prior fixings into our calculations. Second, we have another instrument, the deposit rate, whose accrual period overlaps with our future.
Let’s assume that the fixings from 18 December 2024 to 30 December 2024 have all been 5.00%. There are \(91\) days from 18 December 2024 to 19 March 2025, of which \(12\) have accrued as of 30 December 2024. Additionally, we can compute the SOFR rate from our existing curve up to 31 December 2024, since we have our deposit rate. After that, \(78\) days remain.
Remember,
$$ \begin{equation} R = \bigg[ \prod_{i=0}^N \big[ 1 + SOFR_i \times \frac{days_{i,i+1}}{360} \big] - 1 \bigg] \times \frac{360}{D} \tag{4} \\ \end{equation} $$
Therefore,
$$ \begin{align} 5.25\% &= \big[(1 + 5.00\% \times \frac{1}{360})^{12} \times \frac{1}{df_1} \times (1 + f_{1,2} \times \frac{1}{360})^{78} - 1 \big] \times \frac{360}{91} \nonumber \\ 5.25\% \times \frac{91}{360} &= (1 + 5.00\% \times \frac{1}{360})^{12} \times \frac{1}{df_1} \times (1 + f \times \frac{1}{360})^{78} \nonumber \\ \frac{(1 + 5.25\% \times \frac{91}{360}) \times df_1}{(1 + 5.00\% \times \frac{1}{360})^{12}} &= (1 + f_{1,2} \times \frac{1}{360})^{78} \nonumber \\ f_{1,2} &= \bigg[ \big[ \frac{(1 + 5.25\% \times \frac{91}{360}) \times df_1}{(1 + 5.00\% \times \frac{1}{360})^{12}} \big]^{\frac{1}{78}} - 1 \bigg] \times \frac{360}{1} \nonumber \\ f_{1,2} &= 5.2518\% \nonumber \\ \end{align} $$
Where,
$$ \begin{align} f_{1,2} = \text{the daily compounded interest rate from the time 1 to time 2} \nonumber \\ \end{align} $$
Now that we know the markets expectation of the daily compounded rate from 31 December 2024 to 19 March 2025, we can calculate the discount factor.
$$ \begin{align} df_2 &= \frac{df_1}{(1 + 5.2518\% \times \frac{1}{360})^{78}} \nonumber \\ df_2 &= 0.98854907 \\ \end{align} $$
Fortunately, we won’t have this issue for our other futures. Each successive quarterly future begins accruing at the time that the prior quarterly future matures. For example, SR3H5 will begin accruing on 19 March 2025 and mature on 18 June 2025. As a result, we won’t have to worry about any prior fixings and there are is no overlap between the accrual periods for our futures.
Additionally, we can take advantage of the fact that the futures price is effectively the simple interest equivalent of the daily compounded interest rate that we had to calculate above. As a result, we can bootstrap using a very straightforward approach.
Therefore,
$$ \begin{align} f_{2,3} &= (\frac{df_2}{df_3} - 1) \times \frac{360}{days_{2,3}} \tag{15} \\ df_3 &= \frac{df_2}{1 + f_{2,3} \times \frac{days_{2,3}}{360}} \nonumber \\ df_3 &= \frac{0.98854907}{1 + 5.5000\% \times \frac{91}{360}} \nonumber \\ df_3 &= 0.97499395 \\ \end{align} $$
Where,
$$ \begin{align} f_{2,3} &= \text{the simple interest rate from the time 2 to time 3} \nonumber \\ days_{2,3} &= \text{the number of calendar days from time 2 to time 3} \nonumber \\ \end{align} $$
We could also convert the futures rate to its daily compounding equivalent and use the same formula we used previously,
$$ \begin{align} \big[ 1 + f_{compound} \times \frac{1}{360} \big]^{91} &= 1 + f_{simple} \times \frac{91}{360} \\ f_{compound} &= \bigg[ \big( 1 + f_{simple} \times \frac{91}{360} \big)^{\frac{1}{91}} - 1 \bigg] \times \frac{360}{1} \nonumber \\ f_{compound} &= 5.4625\% \nonumber \\ \nonumber \\ df_3 &= \frac{df_2}{(1 + f_{compound} \times \frac{1}{360})^{91}} \nonumber \\ df_3 &= \frac{0.98854907}{(1 + 5.4625\% \times \frac{1}{360})^{91}} \nonumber \\ df_3 &= 0.97499395 \nonumber \\ \end{align} $$
Applying Equation \(15\) to the two remaining futures,
$$ \begin{align} df_4 &= \frac{df_3}{1 + f_{3,4} \times \frac{days_{3,4}}{360}} \nonumber \\ df_4 &= \frac{0.97499395}{1 + 5.7500\% \times \frac{91}{360}} \nonumber \\ df_4 &= 0.96102570 \\ \nonumber \\ df_5 &= \frac{df_4}{1 + f_{4,5} \times \frac{days_{4,5}}{360}} \nonumber \\ df_5 &= \frac{0.96102570}{1 + 6.0000\% \times \frac{91}{360}} \nonumber \\ df_5 &= 0.94666791 \\ \end{align} $$
Now that we’ve bootstrapped all of our futures, we have,
ID | Instrument | Maturity | Par Rate / Price | Zero rate | Discount Factor |
---|---|---|---|---|---|
\(df_0\) | 2024-12-30 | 1.0 | |||
\(df_1\) | Overnight (ON) Deposit | 2024-12-31 | 5.00% | 5.0691% | 0.99986113 |
\(df_2\) | SR3Z4 | 2025-03-19 | 94.75 | 5.3211% | 0.98854907 |
\(df_3\) | SR3H5 | 2025-06-18 | 94.50 | 5.4372% | 0.97499395 |
\(df_4\) | SR3M5 | 2025-09-17 | 94.25 | 5.5595% | 0.96102570 |
\(df_5\) | SR3U5 | 2025-12-17 | 94.00 | 5.6831% | 0.94666791 |
Swaps
As previously described, swaps are two-legged transactions where the two parties periodically exchange interest based on a fixed interest rate and, in the case of SOFR, a daily compounded interest rate. The fixed interest rate is negotiated by the two parties such that the transaction is worthless when it is traded.
Remember that the value of a payer swap is calculated,
$$ \begin{align} PV_{swap} &= PV_{float} - PV_{fixed} \tag{5} \\ PV_{float} &= \sum_{i=1}^N \text{Notional} \times \bigg[ \prod_{j=1}^M \big[ 1 + SOFR_j \times \frac{days_{j,j+1}}{360} \big] - 1 \bigg] \times \frac{360}{D_i} \times \frac{D_i}{360} \times df_i \tag{6} \\ PV_{fixed} &= \sum_{i=1}^N \text{Notional} \times r_{fixed} \times \frac{D_i}{360} \times df_i \tag{7} \\ \end{align} $$
First, we can remove some of these terms. We can assume the \(\text{Notional}\) is \(1\) and remove the offsetting accrual basis terms. Let’s also begin looking at only the 1Y swap, so there is only 1 annual payment for each leg, which makes \(N = 1\). We also know that the swap is worthless, so \(PV_{swap} = 0\).
Therefore,
$$ \begin{align} PV_{swap} &= 0 \nonumber \\ PV_{float} &= PV_{fixed} \nonumber \\ \bigg[ \prod_{j=1}^M \big[ 1 + SOFR_j \times \frac{days_{j,j+1}}{360} \big] - 1 \bigg] \times df_{M} &= r_{fixed} \times \frac{D_M}{360} \times df_{M} \\ \end{align} $$
Because there is no payment delay, the payments occur on the last day of accrual, which is represented by \(df_{M}\). We can represent \(SOFR_j\) as a forward rate.
Remember that forward rates are calculated, $$ \begin{equation} f_{1,2} = (\frac{df_1}{df_2} - 1) \times \frac{360}{days_{1,2}} \tag{15} \\ \end{equation} $$
So, replacing \(SOFR_j\),
$$ \begin{equation} \begin{split} \bigg[ \prod_{j=1}^M \big[ 1 + ( \frac{df_{j-1}}{df_j} - 1 ) \times \frac{360}{days_{j,j+1}} \times \frac{days_{j,j+1}}{360} \big] - 1 \bigg] \times & \ df_{M} = \\ & r_{fixed} \times \frac{D_M}{360} \times df_{M} \nonumber \\ \end{split} \end{equation} $$
Eliminating offsetting terms, we have,
$$ \begin{equation} \bigg[ \prod_{j=1}^M \big[ \frac{df_{j-1}}{df_j} \big] - 1 \bigg] \times df_{M} = r_{fixed} \times \frac{D_M}{360} \times df_{M} \\ \end{equation} $$
We can also cancel most of the \(df_{j}\) terms,
$$ \begin{equation} \prod_{j=1}^M \big[ \frac{df_{j-1}}{df_j} \big] = \frac{df_0}{\cancel{df_1}} \times \frac{\cancel{df_1}}{\cancel{df_2}} \times \cdots \times \frac{\cancel{df_{M-1}}}{df_M} \nonumber \\ \end{equation} $$
Therefore,
$$ \begin{align} \big[ \frac{df_0}{df_M} - 1 ] \times df_M &= r_{fixed} \times \frac{D_M}{360} \times df_{M} \nonumber \\ df_0 - df_{M} &= r_{fixed} \times \frac{D_M}{360} \times df_{M} \\ \end{align} $$
We’re looking to isolate \(df_{M}\), so,
$$ \begin{align} df_0 - df_{M} &= r_{fixed} \times \frac{D_M}{360} \times df_{M} \nonumber \\ df_0 &= df_{M} + r_{fixed} \times \frac{D_M}{360} \times df_{M} \nonumber \\ df_0 &= df_{M} \times \big[ 1 + r_{fixed} \times \frac{D_M}{360} \big] \nonumber \\ df_{M} &= \frac{df_0}{1 + r_{fixed} \times \frac{D_M}{360}} \\ \end{align} $$
Note that we previously defined \(df_0 = 1\). There are \(365\) days from 30 December 2024 to 30 December 2025. With that information, we can use this formula to calculate the discount factor for our first swap rate,
$$ \begin{align} df_{6} &= \frac{1}{1 + 5.8000\% \times \frac{365}{360}} \nonumber \\ df_{6} &= 0.94446048 \\ \end{align} $$
In order to generalize this function for other swaps, however, we need to incorporate the additional payments that occur on each leg,
$$ \begin{equation} \sum_{i=1}^N \big[ df_{i-1} - df_i \big] = \sum_{i=1}^N \big[ r_{fixed} \times \frac{D_i}{360} \times df_i \big] \\ \end{equation} $$
We can straightforwardly simplify the floating leg, because,
$$ \begin{align} \sum_{i=1}^N \big[ df_{i-1} - df_i \big] &= ( df_0 - df_1 ) + ( df_1 - df_2 ) + \cdots + ( df_{N-1} - df_N ) \nonumber \\ \sum_{i=1}^N \big[ df_{i-1} - df_i \big] &= df_0 + ( df_1 - df_1 ) + ( df_2 - df_2 ) + \cdots + ( df_{N-1} - df_{N-1} ) - df_N \nonumber \\ \sum_{i=1}^N \big[ df_{i-1} - df_i \big] &= df_0 - df_N \\ \end{align} $$
However, the fixed leg is trickier. One thing we can do is remove \(r_{fixed}\) from the summation, since for any swap it is fixed for all periods.
$$ \begin{equation} \sum_{i=1}^N \big[ r_{fixed} \times \frac{D_i}{360} \times df_i \big] = r_{fixed} \times \sum_{i=1}^N \big[ \frac{D_i}{360} \times df_i \big] \nonumber \\ \end{equation} $$
We can also distinguish between the last period and all prior periods. This is helpful because this is an annual swap and, so for each swap, we have the discount factor for each previous payment date (i.e., \(df_i\) is known for all \(i < N\)).
$$ \begin{equation} r_{fixed} \times \sum_{i=1}^N \big[ \frac{D_i}{360} \times df_i \big] = r_{fixed} \times \bigg[ \sum_{i=1}^{N-1} \big[ \frac{D_i}{360} \times df_i \big] + \frac{D_{N}}{360} \times df_N \bigg] \nonumber \\ \end{equation} $$
Let’s define \(A_i\) to be the annuity up to the last cashflow period, the sum of all prior accrual fractions multiplied by the discount factor for each applicable period.
$$ \begin{equation} A_i = \sum_{i=1}^{N-1} \big[ \frac{D_i}{360} \times df_i \big] \\ \end{equation} $$
Note that \(A_i\) is known at when you’re calculating \(df_N\). Plugging that back in, we now have,
$$ \begin{align} r_{fixed} \times \sum_{i=1}^N \big[ \frac{D_i}{360} \times df_i \big] &= r_{fixed} \times \bigg[ A_i + \frac{D_{N}}{360} \times df_N \bigg] \nonumber \\ r_{fixed} \times \sum_{i=1}^N \big[ \frac{D_i}{360} \times df_i \big] &= r_{fixed} \times A_i + r_{fixed} \times \frac{D_N}{360} \times df_N \nonumber \\ \end{align} $$
Putting everything back together, we have,
$$ \begin{align} \sum_{i=1}^N \big[ df_{i-1} - df_i \big] &= \sum_{i=1}^N \big[ r_{fixed} \times \frac{D_i}{360} \times df_i \big] \tag{26} \\ df_0 - df_N &= r_{fixed} \times A_i + r_{fixed} \times \frac{D_N}{360} \times df_N \nonumber \\ df_N + r_{fixed} \times \frac{D_N}{360} \times df_N &= 1 - r_{fixed} \times A_i \nonumber \\ df_N \big[ 1 + r_{fixed} \times \frac{D_N}{360} \big] &= 1 - r_{fixed} \times A_i \nonumber \\ df_N &= \frac{1 - r_{fixed} \times A_i}{1 + r_{fixed} \times \frac{D_N}{360}} \\ \end{align} $$
Applying that to our next swap rate,
$$ \begin{align} A_i &= 0.94446048 \times \frac{365}{360} = 0.95757798 \nonumber \\ df_7 &= \frac{1 - 5.9000\% \times 0.95757798}{1 + 5.9000\% \times \frac{365}{360}} \nonumber \\ df_7 &= 0.89024872 \\ \end{align} $$
And so on,
$$ \begin{align} A_i &= A_{i-1} + 0.89024872 \times \frac{365}{360} = 1.86019126 \nonumber \\ df_8 &= \frac{1 - 6.0000\% \times 1.86019126}{1 + 6.0000\% \times \frac{365}{360}} \nonumber \\ df_8 &= 0.83744401 \\ \end{align} $$
Applying this to the rest of the curve, we now have our fully bootstrapped zero-coupon yield curve.
ID | Instrument | Maturity | Par Rate / Price | Zero rate | Discount Factor |
---|---|---|---|---|---|
\(df_0\) | 2024-12-30 | 1.0 | |||
\(df_1\) | Overnight (ON) Deposit | 2024-12-31 | 5.00% | 5.0691% | 0.99986113 |
\(df_2\) | SR3Z4 | 2025-03-19 | 94.75 | 5.3211% | 0.98854907 |
\(df_3\) | SR3H5 | 2025-06-18 | 94.50 | 5.4372% | 0.97499395 |
\(df_4\) | SR3M5 | 2025-09-17 | 94.25 | 5.5595% | 0.96102570 |
\(df_5\) | SR3U5 | 2025-12-17 | 94.00 | 5.6831% | 0.94666791 |
\(df_6\) | 1Y Swap | 2025-12-30 | 5.80% | 5.7141% | 0.94446048 |
\(df_7\) | 2Y Swap | 2026-12-30 | 5.90% | 5.8127% | 0.89024872 |
\(df_8\) | 3Y Swap | 2027-12-30 | 6.00% | 5.9134% | 0.83744401 |
\(df_9\) | 4Y Swap | 2028-12-30 | 6.10% | 6.0162% | 0.78599025 |
\(df_{10}\) | 5Y Swap | 2029-12-30 | 6.20% | 6.1216% | 0.73620334 |
\(df_{11}\) | 6Y Swap | 2030-12-30 | 6.25% | 6.1736% | 0.69033101 |
\(df_{12}\) | 7Y Swap | 2031-12-30 | 6.30% | 6.2275% | 0.64655496 |
\(df_{13}\) | 8Y Swap | 2032-12-30 | 6.35% | 6.2833% | 0.60471072 |
\(df_{14}\) | 9Y Swap | 2033-12-30 | 6.40% | 6.3410% | 0.56493989 |
\(df_{15}\) | 10Y Swap | 2034-12-30 | 6.45% | 6.4007% | 0.52707263 |
Wrap-up
I wrote this post because I couldn’t find one source that, starting from first principles, went through each step required to bootstrap a SOFR curve. It’s also for myself, for when I forget how to calculate the annuity to bootstrap a curve.
This post shows why and how a zero-coupon yield curve can be bootstrapped from par rates, but this approach to curve building takes many shortcuts. A few that are important to mention:
-
No calendar or business day conventions. We have assumed that every day is a regular business day for the purposes of calculating everything in this post. That, however, is plainly not the case. Incorporating these conventions adds a fair amount of complexity, since some of the terms may no longer offset.
-
I have ignored other conventions like spot delays and payment delays. Each of these adds substantial complexity to the bootstrapping process. I suspect that it may be impossible to bootstrap a curve algebraically these features.
-
I have used annual swaps that pay coupons on the same days as the earlier swaps matured. This is subtle, but, again, simplifies the bootstrapping algorithm. If one of the swaps were removed from the initial instrument selection, I would have had to use some estimation approach to calculate an unknown \(df_i\) for the following swap’s annuity. Likely, I would have used some kind of interpolation or extrapolation on the par swap rates, but that assumes something about the shape of the curve between those points.
-
The SOFR curve uses the same projection and discount rate. When a curve requires a different curve to discount cashflows than itself, swap bootstrapping is substantially more complex.
In order to accurately bootstrap a SOFR curve, or any curve, using a solver is common practice by professionals. Such a solver for this curve could be fairly straightforwardly implemented, however, for other curves, it may require some degree of expertise because of the complexity of some of the features (e.g., using mark-to-market cross-currency swaps to bootstrap a cross-currency curve).
To finish, here is the chart of the par, zero, and forward rates. I note that the curve looks odd, because the par rates are contrived. In practice, you are unlikely to see a curve with spikes in the forward rates, as in here.