Using Fixed-Leg BPS to Solve for a Swap’s Fair Rate
Summary
The document explains why QuantLib’s swap rate helper divides the target net present value by the fixed-leg BPS, then scales by a basis point. The fixed-leg BPS measures how much the fixed leg’s value changes when its coupon rate moves by one basis point. Since the fixed-leg present value varies linearly with the fixed rate, this sensitivity converts a target value into the corresponding fair rate.
During curve bootstrapping, the floating-leg value and any spread contribution determine the target amount for the fixed leg. Dividing that amount by the fixed leg’s value change per basis point yields the rate adjustment in basis-point units, which is then converted to a decimal rate. This describes the linear calculation in the cited helper; the document does not cover the full swap valuation engine or the broader convergence details of curve construction.
Key ideas
- Fixed-leg BPS is the change in fixed-leg NPV for a one-basis-point change in its rate.
- The fixed-leg NPV is linear in the fixed rate under the described setup.
- The target NPV divided by BPS gives the rate adjustment in basis-point units.
- Scaling by the basis-point size converts that adjustment into a decimal swap rate.
Tags
Full text
# Why QuantLib computes the fixed-leg swap rate by this formula?
# Why QuantLib computes the fixed-leg swap rate by this formula?
I'm trying to understand how QuantLib creates (bootstraps) a yield curve from a vanilla swap at the source level. I have the following test code:
```
void testYieldFromIRSwap()
{
Settings::instance().evaluationDate() = Date(1, Jan, 2015);
auto dc = Actual360();
auto h1 = boost::shared_ptr<RateHelper>(new DepositRateHelper
(0.03, Period(1, Years), 0.0, UnitedStates(), Following, false, Actual360()));
auto h2 = boost::shared_ptr<RateHelper>(new DepositRateHelper
(0.04, Period(2, Years), 0.0, UnitedStates(), Following, false, Actual360()));
auto index = boost::shared_ptr<IborIndex>(new EURLibor1Y());
auto h3 = boost::shared_ptr<RateHelper>(
new SwapRateHelper(0.05, Period(3, Years), UnitedStates(), Annual,
Following, Actual360(), index));
std::vector<boost::shared_ptr<RateHelper>> helpers;
helpers.push_back(h1);
helpers.push_back(h2);
helpers.push_back(h3);
boost::shared_ptr<YieldTermStructure> yield(
new PiecewiseYieldCurve<Discount,LogLinear>(
Settings::instance().evaluationDate(), helpers,
Actual360()));
const auto t1 = dc.yearFraction(Date(1, Jan, 2015), Date(1, Jan, 2016)); // 1.014
const auto t2 = dc.yearFraction(Date(1, Jan, 2015), Date(1, Jan, 2017)); // 2.031
const auto t3 = dc.yearFraction(Date(1, Jan, 2015), Date(1, Jan, 2018)); // 3.044
std::cout << yield->discount(0) << std::endl; // Must be 1
std::cout << yield->discount(t1) << std::endl; // 1/((1+0.03) ^ 1.014) = 0.9704721
std::cout << yield->discount(t2) << std::endl; // 1/(1+0.04) ^ 2.031 = 0.9234328
std::cout << yield->discount(t3) << std::endl;
}
```
This is obviously a toy example. I have two deposits that gives the discount rates for the first two years. My swap is an annual 3y swap, I expect the swap gives me the discount rate for the the third year. I do get the value but I don't understand how it's computed.
The code for bootstrapping a discount factor from a swap is:
```
Real SwapRateHelper::impliedQuote() const {
QL_REQUIRE(termStructure_ != 0, "term structure not set");
// we didn't register as observers - force calculation
swap_->recalculate();
// weak implementation... to be improved
static const Spread basisPoint = 1.0e-4;
Real floatingLegNPV = swap_->floatingLegNPV();
Spread spread = spread_.empty() ? 0.0 : spread_->value();
Real spreadNPV = swap_->floatingLegBPS()/basisPoint*spread;
Real totNPV = - (floatingLegNPV+spreadNPV);
Real result = totNPV/(swap_->fixedLegBPS()/basisPoint);
return result;
}
```
Everytime when QuantLib guesses a new discount factor (i.e. iteratively bootstrapping), this function computes a new NPV for the floating leg. I'd expect this NPV be set to equal to the NPV for the fixed leg, from which a new swap rate can be computed.
What I don't understand is this particular line:
```
Real result = totNPV/(swap_->fixedLegBPS()/basisPoint);
```
I think BPS is the sum of accurate interest and is computed by:
```
bps += cp->nominal() * cp->accrualPeriod() * df;
```
Question:
Why do we need to divide the new fixed-leg NPV by BPS and basisPoint? Why basisPoint is set to 1.0e-4 (1 basis point)? What's the point of doing it?
## Answer by Luigi Ballabio (score 6, accepted)
https://quant.stackexchange.com/a/20624
`fixedLegBPS` is the basis-point sensitivity of the fixed leg, that is, how much its NPV changes when the fixed rate changes by one basis point: it's calculated as the NPV corresponding to a fixed rate of 1 bps.
Since the NPV of the fixed leg is linearly proportional to the fixed rate, you can write the equation
```
targetNPV : fixedRate = BPS : 1 basis point
```
The line you highlight simply solves it for the fixed rate.
## Answer by Giancarlo Giuffra (score 0)
https://quant.stackexchange.com/a/20623
it seems the method fixedLegBPS() returns the sum of the discount factors associated to the fixed leg multiplied by 1 basisPoint. So what SwapRateHelper::impliedQuote() returns is actually the new fair rate of the swap to be used in the minimization algorithm.
I have tried to find the implementation of the calculate() method from class VanillaSwap::engine (VanillaSwap.recalculate() calls calculate() which calls its engine calculate() method ) but I haven't been successful. I think you can find confirmation of the behavior of the method fixedLegBPS() there since it calls calculate() and then returns legBPS_[0].
Hope it helps. (and let me know where this implementation is if you find it)Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.