In fixed income markets, the yield curve is a fundamental tool that maps interest rates to different maturities. It underpins the pricing of bonds, swaps, and other financial instruments. How to build a yield curve in C++?
Traders, quants, and risk managers rely on it daily to discount future cash flows. This article explores how to build a basic zero-coupon yield curve using C++, starting from market data and ending with interpolated discount factors. We’ll also compare our results with QuantLib, the industry-standard quantitative finance library.
1. What’s a Yield Curve?
At its core, the yield curve captures how interest rates change with the length of time you lend money. Here’s an example of hypothetical yields across maturities:
Maturity | Yield (%) |
---|---|
Overnight (ON) | 1.00 |
1 Month | 1.20 |
3 Months | 1.35 |
6 Months | 1.50 |
1 Year | 1.80 |
2 Years | 2.10 |
5 Years | 2.60 |
10 Years | 3.00 |
30 Years | 3.20 |
Plotted on a graph, these points form the yield curve — typically upward sloping, as longer maturities usually command higher yields due to risk and time value of money.

However, in stressed environments, the curve can flatten or even invert (e.g., 2-year yield > 10-year yield), often signaling economic uncertainty:

Here is the plot of a hypothetical inverted yield curve, where shorter-term yields are higher than longer-term ones. This often reflects market expectations of economic slowdown or interest rate cuts in the future.
When investors expect future economic weakness, they begin to anticipate interest rate cuts by the central bank. As a result:
- Demand increases for long-term bonds (seen as safe havens), which pushes their prices up and their yields down.
- Meanwhile, short-term rates may remain high due to current central bank policy (e.g. inflation control).
This flips the curve: short-term yields become higher than long-term yields.
2. Key Concepts Behind Yield Curve Construction
Before jumping into code, it’s important to understand how yield curves are built from market data. In practice, we don’t observe a complete yield curve directly, we build (or “bootstrap”) it from liquid instruments such as:
- Deposits (e.g., overnight to 6 months)
- Forward Rate Agreements (FRAs) and Futures
- Swaps (e.g., 1-year to 30-year)
These instruments give us information about specific points on the curve. QuantLib uses these to construct a continuous curve using interpolation (e.g., linear, log-linear) between observed data points.
Some essential building blocks in QuantLib include:
- RateHelpers: Abstractions that turn market quotes (like deposit or swap rates) into bootstrapping constraints.
- DayCount conventions and Calendars: Needed for accurate date and interest calculations.
- YieldTermStructure: The central object representing the term structure of interest rates, which you can query for zero rates, discount factors, or forward rates.
QuantLib lets you define all of this in a modular way, so you can plug in market data and generate an accurate, arbitrage-free curve for pricing and risk analysis.
3. Implement in C++ with Quantlib
To construct a yield curve in QuantLib, you’ll typically bootstrap it from a mix of deposit rates and swaps. QuantLib provides a flexible interface to handle real-world instruments and interpolate the curve. Below is a minimal C++ example that constructs a USD zero curve from deposit and swap rates:
#include <ql/quantlib.hpp>
#include <iostream>
using namespace QuantLib;
int main() {
Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
Date today(25, June, 2025);
Settings::instance().evaluationDate() = today;
// Market quotes
Rate depositRate = 0.015; // 1.5% for 3M deposit
Rate swapRate5Y = 0.025; // 2.5% for 5Y swap
// Quote handles
Handle<Quote> dRate(boost::make_shared<SimpleQuote>(depositRate));
Handle<Quote> sRate(boost::make_shared<SimpleQuote>(swapRate5Y));
// Day counters and conventions
DayCounter depositDayCount = Actual360();
DayCounter curveDayCount = Actual365Fixed();
Thirty360 swapDayCount(Thirty360::BondBasis);
// Instrument helpers
std::vector<boost::shared_ptr<RateHelper>> instruments;
instruments.push_back(boost::make_shared<DepositRateHelper>(
dRate, 3 * Months, 2, calendar, ModifiedFollowing, false, depositDayCount));
instruments.push_back(boost::make_shared<SwapRateHelper>(
sRate, 5 * Years, calendar, Annual, Unadjusted, swapDayCount,
boost::make_shared<Euribor6M>()));
// Construct the yield curve
boost::shared_ptr<YieldTermStructure> yieldCurve =
boost::make_shared<PiecewiseYieldCurve<ZeroYield, Linear>>(
today, instruments, curveDayCount);
// Example output: discount factor at 2Y
Date maturity = calendar.advance(today, 2, Years);
std::cout << "==== Yield Curve ====" << std::endl;
for (int y = 1; y <= 5; ++y) {
Date maturity = calendar.advance(today, y, Years);
double discount = yieldCurve->discount(maturity);
double zeroRate = yieldCurve->zeroRate(maturity, curveDayCount, Compounded, Annual).rate();
std::cout << "Maturity: " << y << "Y\t"
<< "Yield: " << std::fixed << std::setprecision(2) << 100 * zeroRate << "%\t"
<< "Discount: " << std::setprecision(5) << discount
<< std::endl;
}
return 0;
}
This snippet shows how to:
- Set up today’s date and calendar,
- Define deposit and swap instruments as bootstrapping inputs,
- Build a PiecewiseYieldCurve using QuantLib’s helper classes,
- Query the curve for a discount factor.
You can easily extend this with more instruments (FRA, futures, longer swaps) for a realistic curve.
4. Compile, Execute and Plot
After installing quantlib, I compile my code with the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(QuantLibImpliedVolExample)
set(CMAKE_CXX_STANDARD 17)
find_package(PkgConfig REQUIRED)
pkg_check_modules(QUANTLIB REQUIRED QuantLib)
include_directories(${QUANTLIB_INCLUDE_DIRS})
link_directories(${QUANTLIB_LIBRARY_DIRS})
add_executable(yieldcurve ../yieldcurve.cpp)
target_link_libraries(yieldcurve ${QUANTLIB_LIBRARIES})
I run:
mkdir build
cd build
cmake ..
make
And run:
➜ build ./yieldcurve
==== Yield Curve ====
Maturity: 1Y Yield: 1.68% Discount: 0.98345
Maturity: 2Y Yield: 1.89% Discount: 0.96324
Maturity: 3Y Yield: 2.10% Discount: 0.93946
Maturity: 4Y Yield: 2.31% Discount: 0.91272
Maturity: 5Y Yield: 2.52% Discount: 0.88306
And we can plot it too:

So this is the plot of the zero yield curve from 1 to 5 years, based on your QuantLib output. It shows a smooth, gently upward-sloping curve: typical for a healthy interest rate environment.
But why is it so… Linear?
This is due to both the limited number of inputs and the interpolation method applied.
In the example above, the curve is built using QuantLib’s PiecewiseYieldCurve<ZeroYield, Linear>
, which performs linear interpolation between the zero rates of the provided instruments. With only a short-term and a long-term point, this leads to a straight-line interpolation between the two, hence the linear shape.
In reality, yield curves typically exhibit curvature:
- They rise steeply in the short term,
- Then gradually flatten as maturity increases.
This reflects the market’s expectations about interest rates, inflation, and economic growth over time.
To better approximate real-world behavior, the curve can be constructed using:
- A richer set of instruments (e.g., deposits, futures, swaps across many maturities),
- More appropriate interpolation techniques such as
LogLinear
orCubic
.
5. A More Realistic Curve
This version uses LogLinear
interpolation, which interpolates on the log of the discount factors. It results in a more natural term structure than simple linear interpolation, even with only two instruments.
For a truly realistic curve, additional instruments should be incorporated, especially swaps with intermediate maturities:
#include <ql/quantlib.hpp>
#include <iostream>
#include <iomanip>
using namespace QuantLib;
int main() {
Calendar calendar = UnitedStates(UnitedStates::GovernmentBond);
Date today(25, June, 2025);
Settings::instance().evaluationDate() = today;
// Market quotes
Rate depositRate = 0.015; // 1.5% for 3M deposit
Rate swapRate2Y = 0.020; // 2.0% for 2Y swap
Rate swapRate5Y = 0.025; // 2.5% for 5Y swap
Rate swapRate10Y = 0.030; // 3.0% for 10Y swap
// Quote handles
Handle<Quote> dRate(boost::make_shared<SimpleQuote>(depositRate));
Handle<Quote> sRate2Y(boost::make_shared<SimpleQuote>(swapRate2Y));
Handle<Quote> sRate5Y(boost::make_shared<SimpleQuote>(swapRate5Y));
Handle<Quote> sRate10Y(boost::make_shared<SimpleQuote>(swapRate10Y));
// Day counters and conventions
DayCounter depositDayCount = Actual360();
DayCounter curveDayCount = Actual365Fixed();
Thirty360 swapDayCount(Thirty360::BondBasis);
// Instrument helpers
std::vector<boost::shared_ptr<RateHelper>> instruments;
instruments.push_back(boost::make_shared<DepositRateHelper>(
dRate, 3 * Months, 2, calendar, ModifiedFollowing, false, depositDayCount));
instruments.push_back(boost::make_shared<SwapRateHelper>(
sRate2Y, 2 * Years, calendar, Annual, Unadjusted, swapDayCount,
boost::make_shared<Euribor6M>()));
instruments.push_back(boost::make_shared<SwapRateHelper>(
sRate5Y, 5 * Years, calendar, Annual, Unadjusted, swapDayCount,
boost::make_shared<Euribor6M>()));
instruments.push_back(boost::make_shared<SwapRateHelper>(
sRate10Y, 10 * Years, calendar, Annual, Unadjusted, swapDayCount,
boost::make_shared<Euribor6M>()));
// Construct the yield curve with Cubic interpolation
boost::shared_ptr<YieldTermStructure> yieldCurve =
boost::make_shared<PiecewiseYieldCurve<ZeroYield, Cubic>>(
today, instruments, curveDayCount);
// Output: yield curve at each year from 1Y to 10Y
std::cout << "==== Yield Curve ====" << std::endl;
for (int y = 1; y <= 10; ++y) {
Date maturity = calendar.advance(today, y, Years);
double discount = yieldCurve->discount(maturity);
double zeroRate = yieldCurve->zeroRate(maturity, curveDayCount, Compounded, Annual).rate();
std::cout << "Maturity: " << y << "Y\t"
<< "Yield: " << std::fixed << std::setprecision(2) << 100 * zeroRate << "%\t"
<< "Discount: " << std::setprecision(5) << discount
<< std::endl;
}
return 0;
}
And let’s run it again:
➜ build ./yieldcurve
==== Yield Curve ====
Maturity: 1Y Yield: 1.67% Discount: 0.98355
Maturity: 2Y Yield: 2.00% Discount: 0.96122
Maturity: 3Y Yield: 2.20% Discount: 0.93680
Maturity: 4Y Yield: 2.37% Discount: 0.91058
Maturity: 5Y Yield: 2.51% Discount: 0.88320
Maturity: 6Y Yield: 2.64% Discount: 0.85524
Maturity: 7Y Yield: 2.75% Discount: 0.82674
Maturity: 8Y Yield: 2.86% Discount: 0.79790
Maturity: 9Y Yield: 2.96% Discount: 0.76912
Maturity: 10Y Yield: 3.05% Discount: 0.74018
Which gives a better looking yield curve:
