Coverage Report

Created: 2026-10-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/termstructures/yield/oisratehelper.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2009, 2012 Roland Lichters
5
 Copyright (C) 2009, 2012 Ferdinando Ametrano
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <https://www.quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20
21
#include <ql/instruments/makeois.hpp>
22
#include <ql/instruments/simplifynotificationgraph.hpp>
23
#include <ql/cashflows/couponpricer.hpp>
24
#include <ql/cashflows/overnightindexedcoupon.hpp>
25
#include <ql/pricingengines/swap/discountingswapengine.hpp>
26
#include <ql/termstructures/yield/oisratehelper.hpp>
27
#include <ql/utilities/null_deleter.hpp>
28
#include <utility>
29
30
namespace QuantLib {
31
32
    OISRateHelper::OISRateHelper(
33
        Natural settlementDays,
34
        const Period& tenor, // swap maturity
35
        const std::variant<Rate, Handle<Quote>>& fixedRate,
36
        const ext::shared_ptr<OvernightIndex>& overnightIndex,
37
        Handle<YieldTermStructure> discount,
38
        bool telescopicValueDates,
39
        Integer paymentLag,
40
        BusinessDayConvention paymentConvention,
41
        Frequency paymentFrequency,
42
        Calendar paymentCalendar,
43
        const Period& forwardStart,
44
        const std::variant<Spread, Handle<Quote>>& overnightSpread,
45
        Pillar::Choice pillar,
46
        Date customPillarDate,
47
        RateAveraging::Type averagingMethod,
48
        std::optional<bool> endOfMonth,
49
        std::optional<Frequency> fixedPaymentFrequency,
50
        Calendar fixedCalendar,
51
        Natural lookbackDays,
52
        Natural lockoutDays,
53
        bool applyObservationShift,
54
        ext::shared_ptr<FloatingRateCouponPricer> pricer,
55
        DateGeneration::Rule rule,
56
        Calendar overnightCalendar,
57
        BusinessDayConvention convention,
58
        DayCounter fixedDayCount)
59
0
    : RelativeDateRateHelper(fixedRate), settlementDays_(settlementDays), tenor_(tenor),
60
0
      discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates),
61
0
      paymentLag_(paymentLag), paymentConvention_(paymentConvention),
62
0
      paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)),
63
0
      forwardStart_(forwardStart), overnightSpread_(handleFromVariant(overnightSpread)), pillarChoice_(pillar),
64
0
      averagingMethod_(averagingMethod), endOfMonth_(endOfMonth),
65
0
      fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)),
66
0
      overnightCalendar_(std::move(overnightCalendar)), convention_(convention),
67
0
      fixedDayCount_(std::move(fixedDayCount)), lookbackDays_(lookbackDays), lockoutDays_(lockoutDays),
68
0
      applyObservationShift_(applyObservationShift), pricer_(std::move(pricer)), rule_(rule) {
69
0
        initialize(overnightIndex, customPillarDate);
70
0
    }
71
72
    OISRateHelper::OISRateHelper(
73
        const Date& startDate,
74
        const Date& endDate,
75
        const std::variant<Rate, Handle<Quote>>& fixedRate,
76
        const ext::shared_ptr<OvernightIndex>& overnightIndex,
77
        Handle<YieldTermStructure> discount,
78
        bool telescopicValueDates,
79
        Integer paymentLag,
80
        BusinessDayConvention paymentConvention,
81
        Frequency paymentFrequency,
82
        Calendar paymentCalendar,
83
        const std::variant<Spread, Handle<Quote>>& overnightSpread,
84
        Pillar::Choice pillar,
85
        Date customPillarDate,
86
        RateAveraging::Type averagingMethod,
87
        std::optional<bool> endOfMonth,
88
        std::optional<Frequency> fixedPaymentFrequency,
89
        Calendar fixedCalendar,
90
        Natural lookbackDays,
91
        Natural lockoutDays,
92
        bool applyObservationShift,
93
        ext::shared_ptr<FloatingRateCouponPricer> pricer,
94
        DateGeneration::Rule rule,
95
        Calendar overnightCalendar,
96
        BusinessDayConvention convention,
97
        DayCounter fixedDayCount)
98
0
    : RelativeDateRateHelper(fixedRate, false), startDate_(startDate), endDate_(endDate),
99
0
      discountHandle_(std::move(discount)), telescopicValueDates_(telescopicValueDates),
100
0
      paymentLag_(paymentLag), paymentConvention_(paymentConvention),
101
0
      paymentFrequency_(paymentFrequency), paymentCalendar_(std::move(paymentCalendar)),
102
0
      overnightSpread_(handleFromVariant(overnightSpread)), pillarChoice_(pillar),
103
0
      averagingMethod_(averagingMethod), endOfMonth_(endOfMonth),
104
0
      fixedPaymentFrequency_(fixedPaymentFrequency), fixedCalendar_(std::move(fixedCalendar)),
105
0
      overnightCalendar_(std::move(overnightCalendar)), convention_(convention),
106
0
      fixedDayCount_(std::move(fixedDayCount)), lookbackDays_(lookbackDays), lockoutDays_(lockoutDays),
107
0
      applyObservationShift_(applyObservationShift), pricer_(std::move(pricer)), rule_(rule) {
108
0
        initialize(overnightIndex, customPillarDate);
109
0
    }
110
111
    void OISRateHelper::initialize(const ext::shared_ptr<OvernightIndex>& overnightIndex,
112
0
                                   Date customPillarDate) {
113
0
        overnightIndex_ =
114
0
            ext::dynamic_pointer_cast<OvernightIndex>(overnightIndex->clone(termStructureHandle_));
115
        // We want to be notified of changes of fixings, but we don't
116
        // want notifications from termStructureHandle_ (they would
117
        // interfere with bootstrapping.)
118
0
        overnightIndex_->unregisterWith(termStructureHandle_);
119
120
0
        registerWith(overnightIndex_);
121
0
        registerWith(discountHandle_);
122
0
        registerWith(overnightSpread_);
123
124
0
        pillarDate_ = customPillarDate;
125
0
        OISRateHelper::initializeDates();
126
0
    }
127
128
0
    void OISRateHelper::initializeDates() {
129
130
        // 1. do not pass the spread here, as it might be a Quote
131
        //    i.e. it can dynamically change
132
        // 2. input discount curve Handle might be empty now but it could
133
        //    be assigned a curve later; use a RelinkableHandle here
134
0
        auto tmp = MakeOIS(tenor_, overnightIndex_)
135
0
            .withFixedRate(0.0)
136
0
            .withForwardStart(forwardStart_)
137
0
            .withDiscountingTermStructure(discountRelinkableHandle_)
138
0
            .withEffectiveDate(startDate_)
139
0
            .withTerminationDate(endDate_)
140
0
            .withTelescopicValueDates(telescopicValueDates_)
141
0
            .withPaymentLag(paymentLag_)
142
0
            .withPaymentAdjustment(paymentConvention_)
143
0
            .withPaymentFrequency(paymentFrequency_)
144
0
            .withPaymentCalendar(paymentCalendar_)
145
0
            .withAveragingMethod(averagingMethod_)
146
0
            .withLookbackDays(lookbackDays_)
147
0
            .withLockoutDays(lockoutDays_)
148
0
            .withRule(rule_)
149
0
            .withConvention(convention_)
150
0
            .withTerminationDateConvention(convention_)
151
0
            .withObservationShift(applyObservationShift_);
152
0
        if (endOfMonth_) {
153
0
            tmp.withEndOfMonth(*endOfMonth_);
154
0
        }
155
0
        if (fixedPaymentFrequency_) {
156
0
            tmp.withFixedLegPaymentFrequency(*fixedPaymentFrequency_);
157
0
        }
158
0
        if (!fixedCalendar_.empty()) {
159
0
            tmp.withFixedLegCalendar(fixedCalendar_);
160
0
        }
161
0
        if (!fixedDayCount_.empty()) {
162
0
            tmp.withFixedLegDayCount(fixedDayCount_);
163
0
        }
164
0
        if (!overnightCalendar_.empty()) {
165
0
            tmp.withOvernightLegCalendar(overnightCalendar_);
166
0
        }
167
        // only set settlementDays when no explicit start date, to avoid conflict
168
0
        if (startDate_ == Date() && settlementDays_ != Null<Natural>())
169
0
            tmp.withSettlementDays(settlementDays_);
170
0
        swap_ = tmp;
171
172
0
        if (pricer_)
173
0
            setCouponPricer(swap_->overnightLeg(), pricer_);
174
175
0
        simplifyNotificationGraph(*swap_, true);
176
177
0
        earliestDate_ = swap_->startDate();
178
0
        maturityDate_ = swap_->maturityDate();
179
180
0
        Date lastPaymentDate = std::max(swap_->overnightLeg().back()->date(),
181
0
                                        swap_->fixedLeg().back()->date());
182
0
        Date lastFixingDate =
183
0
            ext::dynamic_pointer_cast<OvernightIndexedCoupon>(swap_->overnightLeg().back())->fixingDate();
184
0
        Date fixingEndDate =
185
0
            overnightIndex_->maturityDate(overnightIndex_->valueDate(lastFixingDate));
186
0
        latestRelevantDate_ = latestDate_ = std::max({maturityDate_, lastPaymentDate, fixingEndDate});
187
188
0
        switch (pillarChoice_) {
189
0
          case Pillar::MaturityDate:
190
0
            pillarDate_ = maturityDate_;
191
0
            break;
192
0
          case Pillar::LastRelevantDate:
193
0
            pillarDate_ = latestRelevantDate_;
194
0
            break;
195
0
          case Pillar::CustomDate:
196
            // pillarDate_ already assigned at construction time
197
0
            QL_REQUIRE(pillarDate_ >= earliestDate_,
198
0
                       "pillar date (" << pillarDate_ << ") must be later "
199
0
                       "than or equal to the instrument's earliest date (" <<
200
0
                       earliestDate_ << ")");
201
0
            QL_REQUIRE(pillarDate_ <= latestRelevantDate_,
202
0
                       "pillar date (" << pillarDate_ << ") must be before "
203
0
                       "or equal to the instrument's latest relevant date (" <<
204
0
                       latestRelevantDate_ << ")");
205
0
            break;
206
0
          default:
207
0
            QL_FAIL("unknown Pillar::Choice(" << Integer(pillarChoice_) << ")");
208
0
        }
209
0
    }
210
211
0
    void OISRateHelper::setTermStructure(YieldTermStructure* t) {
212
        // do not set the relinkable handle as an observer -
213
        // force recalculation when needed
214
0
        bool observer = false;
215
216
0
        ext::shared_ptr<YieldTermStructure> temp(t, null_deleter());
217
0
        termStructureHandle_.linkTo(temp, observer);
218
219
0
        if (discountHandle_.empty())
220
0
            discountRelinkableHandle_.linkTo(temp, observer);
221
0
        else
222
0
            discountRelinkableHandle_.linkTo(*discountHandle_, observer);
223
224
0
        RelativeDateRateHelper::setTermStructure(t);
225
0
    }
226
227
0
    Real OISRateHelper::impliedQuote() const {
228
0
        QL_REQUIRE(termStructure_ != nullptr, "term structure not set");
229
        // we didn't register as observers - force calculation
230
0
        swap_->deepUpdate();
231
        // weak implementation... to be improved
232
0
        static const Spread basisPoint = 1.0e-4;
233
0
        Real floatingLegNPV = swap_->overnightLegNPV();
234
0
        Spread spread = overnightSpread_.empty() ? 0.0 : overnightSpread_->value();
235
0
        Real spreadNPV = swap_->overnightLegBPS()/basisPoint*spread;
236
0
        Real totNPV = - (floatingLegNPV+spreadNPV);
237
0
        Real result = totNPV/(swap_->fixedLegBPS()/basisPoint);
238
0
        return result;
239
0
    }
240
241
0
    void OISRateHelper::accept(AcyclicVisitor& v) {
242
0
        auto* v1 = dynamic_cast<Visitor<OISRateHelper>*>(&v);
243
0
        if (v1 != nullptr)
244
0
            v1->visit(*this);
245
0
        else
246
0
            RateHelper::accept(v);
247
0
    }
248
249
}