/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 | | } |