Coverage Report

Created: 2026-10-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/cashflows/rangeaccrual.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2006, 2007 Giorgio Facchinetti
5
 Copyright (C) 2006, 2007 Mario Pucci
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
22
#include <ql/cashflows/cashflowvectors.hpp>
23
#include <ql/cashflows/rangeaccrual.hpp>
24
#include <ql/math/functional.hpp>
25
#include <ql/indexes/iborindex.hpp>
26
#include <ql/math/distributions/normaldistribution.hpp>
27
#include <ql/pricingengines/blackformula.hpp>
28
#include <ql/termstructures/yieldtermstructure.hpp>
29
#include <ql/time/schedule.hpp>
30
#include <cmath>
31
#include <utility>
32
33
namespace QuantLib {
34
35
    //===========================================================================//
36
    //                         RangeAccrualFloatersCoupon                        //
37
    //===========================================================================//
38
39
    RangeAccrualFloatersCoupon::RangeAccrualFloatersCoupon(
40
        const Date& paymentDate,
41
        Real nominal,
42
        const ext::shared_ptr<IborIndex>& index,
43
        const Date& startDate, // S
44
        const Date& endDate,   // T
45
        Natural fixingDays,
46
        const DayCounter& dayCounter,
47
        Real gearing,
48
        Rate spread,
49
        const Date& refPeriodStart,
50
        const Date& refPeriodEnd,
51
        Schedule observationSchedule,
52
        Real lowerTrigger, // l
53
        Real upperTrigger  // u
54
        )
55
0
    : FloatingRateCoupon(paymentDate,
56
0
                         nominal,
57
0
                         startDate,
58
0
                         endDate,
59
0
                         fixingDays,
60
0
                         index,
61
0
                         gearing,
62
0
                         spread,
63
0
                         refPeriodStart,
64
0
                         refPeriodEnd,
65
0
                         dayCounter),
66
0
      observationSchedule_(std::move(observationSchedule)), lowerTrigger_(lowerTrigger),
67
0
      upperTrigger_(upperTrigger) {
68
69
0
        QL_REQUIRE(lowerTrigger_<upperTrigger,
70
0
                   "lowerTrigger_>=upperTrigger");
71
0
        QL_REQUIRE(observationSchedule_.startDate()==startDate,
72
0
                   "incompatible start date");
73
0
        QL_REQUIRE(observationSchedule_.endDate()==endDate,
74
0
                   "incompatible end date");
75
76
0
        observationDates_ = observationSchedule_.dates();
77
0
        observationDates_.pop_back();                       //remove end date
78
0
        observationDates_.erase(observationDates_.begin()); //remove start date
79
0
        observationsNo_ = observationDates_.size();
80
81
0
        const Handle<YieldTermStructure>& rateCurve =
82
0
            index->forwardingTermStructure();
83
0
        Date referenceDate = rateCurve->referenceDate();
84
85
0
        startTime_ = dayCounter.yearFraction(referenceDate, startDate);
86
0
        endTime_ = dayCounter.yearFraction(referenceDate, endDate);
87
0
        for(Size i=0;i<observationsNo_;i++) {
88
0
            observationTimes_.push_back(
89
0
                dayCounter.yearFraction(referenceDate, observationDates_[i]));
90
0
        }
91
0
    }
Unexecuted instantiation: QuantLib::RangeAccrualFloatersCoupon::RangeAccrualFloatersCoupon(QuantLib::Date const&, double, boost::shared_ptr<QuantLib::IborIndex> const&, QuantLib::Date const&, QuantLib::Date const&, unsigned int, QuantLib::DayCounter const&, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Schedule, double, double)
Unexecuted instantiation: QuantLib::RangeAccrualFloatersCoupon::RangeAccrualFloatersCoupon(QuantLib::Date const&, double, boost::shared_ptr<QuantLib::IborIndex> const&, QuantLib::Date const&, QuantLib::Date const&, unsigned int, QuantLib::DayCounter const&, double, double, QuantLib::Date const&, QuantLib::Date const&, QuantLib::Schedule, double, double)
92
93
0
    void RangeAccrualFloatersCoupon::accept(AcyclicVisitor& v) {
94
0
        auto* v1 = dynamic_cast<Visitor<RangeAccrualFloatersCoupon>*>(&v);
95
0
        if (v1 != nullptr)
96
0
            v1->visit(*this);
97
0
        else
98
0
            FloatingRateCoupon::accept(v);
99
0
    }
100
101
    Real RangeAccrualFloatersCoupon::priceWithoutOptionality(
102
0
           const Handle<YieldTermStructure>& discountingCurve) const {
103
0
        return accrualPeriod() * (gearing_*indexFixing()+spread_) *
104
0
               nominal() * discountingCurve->discount(date());
105
0
    }
106
107
108
    //=======================================================================//
109
    //                        RangeAccrualPricer                             //
110
    //=======================================================================//
111
112
0
    void RangeAccrualPricer::initialize(const FloatingRateCoupon& coupon){
113
0
        coupon_ =  dynamic_cast<const RangeAccrualFloatersCoupon*>(&coupon);
114
0
        QL_REQUIRE(coupon_, "range-accrual coupon required");
115
0
        gearing_ = coupon_->gearing();
116
0
        spread_ = coupon_->spread();
117
118
0
        Date paymentDate = coupon_->date();
119
120
0
        ext::shared_ptr<IborIndex> index =
121
0
            ext::dynamic_pointer_cast<IborIndex>(coupon_->index());
122
0
        const Handle<YieldTermStructure>& rateCurve =
123
0
            index->forwardingTermStructure();
124
0
        discount_ = rateCurve->discount(paymentDate);
125
0
        accrualFactor_ = coupon_->accrualPeriod();
126
0
        spreadLegValue_ = spread_ * accrualFactor_* discount_;
127
128
0
        startTime_ = coupon_->startTime();
129
0
        endTime_ = coupon_->endTime();
130
0
        observationTimes_ = coupon_->observationTimes();
131
0
        lowerTrigger_ = coupon_->lowerTrigger();
132
0
        upperTrigger_ = coupon_->upperTrigger();
133
0
        observationsNo_ = coupon_->observationsNo();
134
135
0
        const std::vector<Date> &observationDates =
136
0
            coupon_->observationSchedule().dates();
137
0
        QL_REQUIRE(observationDates.size()==observationsNo_+2,
138
0
                   "incompatible size of initialValues vector");
139
0
        initialValues_= std::vector<Real>(observationDates.size(),0.);
140
141
0
        Calendar calendar = index->fixingCalendar();
142
0
        for(Size i=0; i<observationDates.size(); i++) {
143
0
            initialValues_[i]=index->fixing(
144
0
                calendar.advance(observationDates[i],
145
0
                                 -static_cast<Integer>(coupon_->fixingDays()),
146
0
                                 Days));
147
0
        }
148
149
0
    }
150
151
0
    Real RangeAccrualPricer::swapletRate() const {
152
0
        return swapletPrice()/(accrualFactor_*discount_);
153
0
    }
154
155
0
    Real RangeAccrualPricer::capletPrice(Rate) const {
156
0
        QL_FAIL("RangeAccrualPricer::capletPrice not implemented");
157
0
    }
158
159
0
    Rate RangeAccrualPricer::capletRate(Rate) const {
160
0
        QL_FAIL("RangeAccrualPricer::capletRate not implemented");
161
0
    }
162
163
0
    Real RangeAccrualPricer::floorletPrice(Rate) const {
164
0
        QL_FAIL("RangeAccrualPricer::floorletPrice not implemented");
165
0
    }
166
167
0
    Rate RangeAccrualPricer::floorletRate(Rate) const {
168
0
        QL_FAIL("RangeAccrualPricer::floorletRate not implemented");
169
0
    }
170
171
    //===========================================================================//
172
    //                          RangeAccrualPricerByBgm                          //
173
    //===========================================================================//
174
    RangeAccrualPricerByBgm::RangeAccrualPricerByBgm(Real correlation,
175
                                                     ext::shared_ptr<SmileSection> smilesOnExpiry,
176
                                                     ext::shared_ptr<SmileSection> smilesOnPayment,
177
                                                     bool withSmile,
178
                                                     bool byCallSpread)
179
0
    : correlation_(correlation), withSmile_(withSmile), byCallSpread_(byCallSpread),
180
0
      smilesOnExpiry_(std::move(smilesOnExpiry)), smilesOnPayment_(std::move(smilesOnPayment)) {}
Unexecuted instantiation: QuantLib::RangeAccrualPricerByBgm::RangeAccrualPricerByBgm(double, boost::shared_ptr<QuantLib::SmileSection>, boost::shared_ptr<QuantLib::SmileSection>, bool, bool)
Unexecuted instantiation: QuantLib::RangeAccrualPricerByBgm::RangeAccrualPricerByBgm(double, boost::shared_ptr<QuantLib::SmileSection>, boost::shared_ptr<QuantLib::SmileSection>, bool, bool)
181
0
    Real RangeAccrualPricerByBgm::swapletPrice() const{
182
183
0
        Real result = 0.;
184
0
        const Real deflator = discount_*initialValues_[0];
185
0
        for(Size i=0;i<observationsNo_;i++){
186
0
            Real digitalFloater = digitalRangePrice(lowerTrigger_, upperTrigger_,initialValues_[i+1],
187
0
                                                     observationTimes_[i], deflator);
188
0
            result += digitalFloater;
189
0
        }
190
0
        return gearing_ *(result*accrualFactor_/observationsNo_)+ spreadLegValue_;
191
0
    }
192
193
    std::vector<Real> RangeAccrualPricerByBgm::driftsOverPeriod(Real U,
194
                                                                Real lambdaS,
195
                                                                Real lambdaT,
196
0
                                                                Real correlation) const{
197
0
        std::vector<Real> result;
198
199
0
        const Real p = (U-startTime_)/accrualFactor_;
200
0
        const Real q = (endTime_-U)/accrualFactor_;
201
0
        const Real L0T = initialValues_.back();
202
203
0
        const Real driftBeforeFixing =
204
0
                p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)*(p*lambdaT*lambdaT + q*lambdaS*lambdaT*correlation) +
205
0
                q*lambdaS*lambdaS + p*lambdaS*lambdaT*correlation
206
0
                -0.5*lambda(U,lambdaS,lambdaT)*lambda(U,lambdaS,lambdaT);
207
0
        const Real driftAfterFixing = (p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)-0.5)*lambdaT*lambdaT;
208
209
0
        result.push_back(driftBeforeFixing);
210
0
        result.push_back(driftAfterFixing);
211
212
0
        return result;
213
0
    }
214
215
    std::vector<Real> RangeAccrualPricerByBgm::lambdasOverPeriod(Real U,
216
                                                                   Real lambdaS,
217
0
                                                                   Real lambdaT) const{
218
0
        std::vector<Real> result;
219
220
0
        const Real p = (U-startTime_)/accrualFactor_;
221
0
        const Real q = (endTime_-U)/accrualFactor_;
222
223
0
        const Real lambdaBeforeFixing = q*lambdaS + p*lambdaT;
224
0
        const Real lambdaAfterFixing = lambdaT;
225
226
0
        result.push_back(lambdaBeforeFixing);
227
0
        result.push_back(lambdaAfterFixing);
228
229
0
        return result;
230
0
    }
231
    Real RangeAccrualPricerByBgm::drift(Real U,
232
                                          Real lambdaS,
233
                                          Real lambdaT,
234
0
                                          Real correlation) const{
235
0
        Real result;
236
237
0
        const Real p = (U-startTime_)/accrualFactor_;
238
0
        const Real q = (endTime_-U)/accrualFactor_;
239
0
        const Real L0T = initialValues_.back();
240
241
0
        const Real driftBeforeFixing =
242
0
                p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)*(p*lambdaT*lambdaT + q*lambdaS*lambdaT*correlation) +
243
0
                q*lambdaS*lambdaS + p*lambdaS*lambdaT*correlation;
244
0
        const Real driftAfterFixing = (p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)-0.5)*lambdaT*lambdaT;
245
246
0
        if(startTime_ > 0){result = driftBeforeFixing;}
247
0
        else {result = driftAfterFixing;}
248
249
0
        return result;
250
0
    }
251
252
    Real RangeAccrualPricerByBgm::lambda(Real U,
253
                                           Real lambdaS,
254
0
                                           Real lambdaT) const{
255
0
        Real result;
256
257
0
        const Real p = (U-startTime_)/accrualFactor_;
258
0
        const Real q = (endTime_-U)/accrualFactor_;
259
260
0
        if(startTime_ > 0){result = q*lambdaS + p*lambdaT;}
261
0
        else {result = lambdaT;}
262
263
0
        return result;
264
0
    }
265
266
267
    Real RangeAccrualPricerByBgm::derDriftDerLambdaS(Real U,
268
                                                        Real lambdaS,
269
                                                        Real lambdaT,
270
0
                                                        Real correlation) const{
271
0
        Real result;
272
273
0
        const Real p = (U-startTime_)/accrualFactor_;
274
0
        const Real q = (endTime_-U)/accrualFactor_;
275
0
        const Real L0T = initialValues_.back();
276
277
0
        const Real driftBeforeFixing =
278
0
                p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)*(q*lambdaT*correlation) +
279
0
                2*q*lambdaS + p*lambdaT*correlation;
280
0
        const Real driftAfterFixing = 0.;
281
282
0
        if(startTime_ > 0){result = driftBeforeFixing;}
283
0
        else {result = driftAfterFixing;}
284
285
0
        return result;
286
0
    }
287
288
0
    Real RangeAccrualPricerByBgm::derLambdaDerLambdaS(Real U) const {
289
290
0
        if (startTime_>0) {
291
0
            Real q = (endTime_-U)/accrualFactor_;
292
0
            return q;
293
0
        } else
294
0
            return 0.0;
295
296
0
    }
297
298
    Real RangeAccrualPricerByBgm::derDriftDerLambdaT(Real U,
299
                                                        Real lambdaS,
300
                                                        Real lambdaT,
301
0
                                                        Real correlation) const{
302
0
        Real result;
303
304
0
        const Real p = (U-startTime_)/accrualFactor_;
305
0
        const Real q = (endTime_-U)/accrualFactor_;
306
0
        const Real L0T = initialValues_.back();
307
308
0
        const Real driftBeforeFixing =
309
0
                p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)*(2*p*lambdaT + q*lambdaS*correlation) +
310
0
                + p*lambdaS*correlation;
311
0
        const Real driftAfterFixing = (p*accrualFactor_*L0T/(1.+L0T*accrualFactor_)-0.5)*2*lambdaT;
312
313
0
        if(startTime_ > 0){result = driftBeforeFixing;}
314
0
        else {result = driftAfterFixing;}
315
316
0
        return result;
317
0
    }
318
319
0
    Real RangeAccrualPricerByBgm::derLambdaDerLambdaT(Real U) const {
320
321
0
        if (startTime_>0) {
322
0
            Real p = (U-startTime_)/accrualFactor_;
323
0
            return p;
324
0
        } else
325
0
            return 0.0;
326
327
0
    }
328
329
    Real RangeAccrualPricerByBgm::digitalRangePrice(Real lowerTrigger,
330
                                                      Real upperTrigger,
331
                                                      Real initialValue,
332
                                                      Real expiry,
333
0
                                                      Real deflator) const{
334
0
            const Real lowerPrice = digitalPrice(lowerTrigger, initialValue, expiry, deflator);
335
0
            const Real upperPrice = digitalPrice(upperTrigger, initialValue, expiry, deflator);
336
0
            const Real result =  lowerPrice - upperPrice;
337
0
            QL_REQUIRE(result >=0.,
338
0
                "RangeAccrualPricerByBgm::digitalRangePrice:\n digitalPrice("<<upperTrigger<<
339
0
                "): "<<upperPrice<<" >  digitalPrice("<<lowerTrigger<<"): "<<lowerPrice);
340
0
            return result;
341
342
0
    }
343
    Real RangeAccrualPricerByBgm::digitalPrice(Real strike,
344
                                        Real initialValue,
345
                                        Real expiry,
346
0
                                        Real deflator) const {
347
0
        Real result = deflator;
348
0
        if(strike>eps_/2){
349
0
            if(withSmile_)
350
0
                result = digitalPriceWithSmile(strike, initialValue, expiry, deflator);
351
0
            else
352
0
                result = digitalPriceWithoutSmile(strike, initialValue, expiry, deflator);
353
0
        }
354
0
        return result;
355
0
    }
356
357
    Real RangeAccrualPricerByBgm::digitalPriceWithoutSmile(Real strike,
358
                                        Real initialValue,
359
                                        Real expiry,
360
0
                                        Real deflator) const {
361
362
0
        Real lambdaS = smilesOnExpiry_->volatility(strike);
363
0
        Real lambdaT = smilesOnPayment_->volatility(strike);
364
365
0
        std::vector<Real> lambdaU = lambdasOverPeriod(expiry, lambdaS, lambdaT);
366
0
        const Real variance =
367
0
            startTime_*lambdaU[0]*lambdaU[0]+(expiry-startTime_)*lambdaU[1]*lambdaU[1];
368
369
0
        Real lambdaSATM = smilesOnExpiry_->volatility(initialValue);
370
0
        Real lambdaTATM = smilesOnPayment_->volatility(initialValue);
371
        //drift of Lognormal process (of Libor) "a_U()" nel paper
372
0
        std::vector<Real> muU = driftsOverPeriod(expiry, lambdaSATM, lambdaTATM, correlation_);
373
0
        const Real adjustment = (startTime_*muU[0]+(expiry-startTime_)*muU[1]);
374
375
376
0
       Real d2 = (std::log(initialValue/strike) + adjustment - 0.5*variance)/std::sqrt(variance);
377
378
0
       CumulativeNormalDistribution phi;
379
0
       const Real result = deflator*phi(d2);
380
381
0
       QL_REQUIRE(result > 0.,
382
0
           "RangeAccrualPricerByBgm::digitalPriceWithoutSmile: result< 0. Result:"<<result);
383
0
       QL_REQUIRE(result/deflator <= 1.,
384
0
            "RangeAccrualPricerByBgm::digitalPriceWithoutSmile: result/deflator > 1. Ratio: "
385
0
            << result/deflator << " result: " << result<< " deflator: " << deflator);
386
387
0
       return result;
388
0
    }
389
390
    Real RangeAccrualPricerByBgm::digitalPriceWithSmile(Real strike,
391
                                        Real initialValue,
392
                                        Real expiry,
393
0
                                        Real deflator) const {
394
0
        Real result;
395
0
        if (byCallSpread_) {
396
397
            // Previous strike
398
0
            const Real previousStrike = strike - eps_/2;
399
0
            Real lambdaS = smilesOnExpiry_->volatility(previousStrike);
400
0
            Real lambdaT = smilesOnPayment_->volatility(previousStrike);
401
402
            //drift of Lognormal process (of Libor) "a_U()" nel paper
403
0
            std::vector<Real> lambdaU = lambdasOverPeriod(expiry, lambdaS, lambdaT);
404
0
            const Real previousVariance = std::max(startTime_, 0.)*lambdaU[0]*lambdaU[0]+
405
0
                         std::min(expiry-startTime_, expiry)*lambdaU[1]*lambdaU[1];
406
407
0
            Real lambdaSATM = smilesOnExpiry_->volatility(initialValue);
408
0
            Real lambdaTATM = smilesOnPayment_->volatility(initialValue);
409
0
            std::vector<Real> muU = driftsOverPeriod(expiry, lambdaSATM, lambdaTATM, correlation_);
410
0
            const Real previousAdjustment = std::exp(std::max(startTime_, 0.)*muU[0] +
411
0
                                         std::min(expiry-startTime_, expiry)*muU[1]);
412
0
            const Real previousForward = initialValue * previousAdjustment ;
413
414
            // Next strike
415
0
            const Real nextStrike = strike + eps_/2;
416
0
            lambdaS = smilesOnExpiry_->volatility(nextStrike);
417
0
            lambdaT = smilesOnPayment_->volatility(nextStrike);
418
419
0
            lambdaU = lambdasOverPeriod(expiry, lambdaS, lambdaT);
420
0
            const Real nextVariance = std::max(startTime_, 0.)*lambdaU[0]*lambdaU[0]+
421
0
                         std::min(expiry-startTime_, expiry)*lambdaU[1]*lambdaU[1];
422
            //drift of Lognormal process (of Libor) "a_U()" nel paper
423
0
            muU = driftsOverPeriod(expiry, lambdaSATM, lambdaTATM, correlation_);
424
0
            const Real nextAdjustment = std::exp(std::max(startTime_, 0.)*muU[0] +
425
0
                                         std::min(expiry-startTime_, expiry)*muU[1]);
426
0
            const Real nextForward = initialValue * nextAdjustment ;
427
428
0
            result = callSpreadPrice(previousForward,nextForward,previousStrike, nextStrike,
429
0
                                                    deflator, previousVariance, nextVariance);
430
431
0
        }
432
0
        else{
433
0
            result = digitalPriceWithoutSmile(strike, initialValue, expiry, deflator)+
434
0
                     smileCorrection(strike, initialValue, expiry, deflator);
435
0
        }
436
437
0
        QL_REQUIRE(result > -std::sqrt(eps_),
438
0
            "RangeAccrualPricerByBgm::digitalPriceWithSmile: result< 0 Result:"<<result);
439
0
        QL_REQUIRE(result/deflator <=  1.0 + std::pow(eps_,.2),
440
0
            "RangeAccrualPricerByBgm::digitalPriceWithSmile: result/deflator > 1. Ratio: "
441
0
            << result/deflator << " result: " << result<< " deflator: " << deflator);
442
443
0
        return result;
444
0
    }
445
446
    Real RangeAccrualPricerByBgm::smileCorrection(Real strike,
447
                                        Real forward,
448
                                        Real expiry,
449
0
                                        Real deflator) const {
450
451
0
        const Real previousStrike = strike - eps_/2;
452
0
        const Real nextStrike = strike + eps_/2;
453
454
0
        const Real derSmileS = (smilesOnExpiry_->volatility(nextStrike)-
455
0
                                 smilesOnExpiry_->volatility(previousStrike))/eps_;
456
0
        const Real derSmileT = (smilesOnPayment_->volatility(nextStrike)-
457
0
                                 smilesOnPayment_->volatility(previousStrike))/eps_;
458
459
0
        Real lambdaS = smilesOnExpiry_->volatility(strike);
460
0
        Real lambdaT = smilesOnPayment_->volatility(strike);
461
        //Real lambdaU = lambda(expiry, lambdaS, lambdaT);
462
463
0
        Real derLambdaDerK = derLambdaDerLambdaS(expiry) * derSmileS +
464
0
                             derLambdaDerLambdaT(expiry) * derSmileT;
465
        //Real derDriftDerK = derDriftDerLambdaS(expiry, lambdaS, lambdaT, correlation_)*derSmileS +
466
        //                      derDriftDerLambdaT(expiry, lambdaS, lambdaT, correlation_)*derSmileT +
467
        //                      lambdaU * derLambdaDerK;
468
469
0
        Real lambdaSATM = smilesOnExpiry_->volatility(forward);
470
0
        Real lambdaTATM = smilesOnPayment_->volatility(forward);
471
0
        std::vector<Real> lambdasOverPeriodU = lambdasOverPeriod(expiry, lambdaS, lambdaT);
472
        //drift of Lognormal process (of Libor) "a_U()" nel paper
473
0
        std::vector<Real> muU = driftsOverPeriod(expiry, lambdaSATM, lambdaTATM, correlation_);
474
475
0
        const Real variance = std::max(startTime_, 0.)*lambdasOverPeriodU[0]*lambdasOverPeriodU[0] +
476
0
                       std::min(expiry-startTime_, expiry)*lambdasOverPeriodU[1]*lambdasOverPeriodU[1];
477
478
0
        const Real forwardAdjustment = std::exp(std::max(startTime_, 0.)*muU[0] +
479
0
                                         std::min(expiry-startTime_, expiry)*muU[1]);
480
0
        const Real forwardAdjusted = forward * forwardAdjustment;
481
482
0
        const Real d1 = (std::log(forwardAdjusted/strike)+0.5*variance)/std::sqrt(variance);
483
484
0
        const Real sqrtOfTimeToExpiry = (std::max(startTime_, 0.)*lambdasOverPeriodU[0] +
485
0
                                std::min(expiry-startTime_, expiry)*lambdasOverPeriodU[1])*
486
0
                                (1./std::sqrt(variance));
487
488
0
        CumulativeNormalDistribution phi;
489
0
        NormalDistribution psi;
490
0
        Real result = - forwardAdjusted*psi(d1)*sqrtOfTimeToExpiry*derLambdaDerK ;
491
                       // - forwardAdjusted*phi(d1)*expiry*derDriftDerK;
492
493
0
        result *= deflator;
494
495
0
        QL_REQUIRE(std::fabs(result/deflator) <= 1.0 + std::pow(eps_,.2),
496
0
            "RangeAccrualPricerByBgm::smileCorrection: abs(result/deflator) > 1. Ratio: "
497
0
            << result/deflator << " result: " << result<< " deflator: " << deflator);
498
499
0
        return result;
500
0
    }
501
502
    Real RangeAccrualPricerByBgm::callSpreadPrice(
503
                                            Real previousForward,
504
                                            Real nextForward,
505
                                            Real previousStrike,
506
                                            Real nextStrike,
507
                                            Real deflator,
508
                                            Real previousVariance,
509
0
                                            Real nextVariance) const{
510
0
         const Real nextCall =
511
0
            blackFormula(Option::Call, nextStrike, nextForward, std::sqrt(nextVariance), deflator);
512
0
         const Real previousCall =
513
0
            blackFormula(Option::Call, previousStrike, previousForward, std::sqrt(previousVariance), deflator);
514
515
0
         QL_ENSURE(nextCall <previousCall,"RangeAccrualPricerByBgm::callSpreadPrice: nextCall > previousCall"
516
0
            "\n nextCall: strike :" << nextStrike << "; variance: " << nextVariance <<
517
0
            " adjusted initial value " << nextForward <<
518
0
            "\n previousCall: strike :" << previousStrike << "; variance: " << previousVariance <<
519
0
            " adjusted initial value " << previousForward );
520
521
0
         const Real result = (previousCall-nextCall)/(nextStrike-previousStrike);
522
523
0
         return result;
524
0
    }
525
526
527
    RangeAccrualLeg::RangeAccrualLeg(Schedule schedule, ext::shared_ptr<IborIndex> index)
528
0
    : schedule_(std::move(schedule)), index_(std::move(index)) {}
529
530
0
    RangeAccrualLeg& RangeAccrualLeg::withNotionals(Real notional) {
531
0
        notionals_ = std::vector<Real>(1,notional);
532
0
        return *this;
533
0
    }
534
535
    RangeAccrualLeg& RangeAccrualLeg::withNotionals(
536
0
                                         const std::vector<Real>& notionals) {
537
0
        notionals_ = notionals;
538
0
        return *this;
539
0
    }
540
541
    RangeAccrualLeg& RangeAccrualLeg::withPaymentDayCounter(
542
0
                                               const DayCounter& dayCounter) {
543
0
        paymentDayCounter_ = dayCounter;
544
0
        return *this;
545
0
    }
546
547
    RangeAccrualLeg& RangeAccrualLeg::withPaymentAdjustment(
548
0
                                           BusinessDayConvention convention) {
549
0
        paymentAdjustment_ = convention;
550
0
        return *this;
551
0
    }
552
553
0
    RangeAccrualLeg& RangeAccrualLeg::withFixingDays(Natural fixingDays) {
554
0
        fixingDays_ = std::vector<Natural>(1,fixingDays);
555
0
        return *this;
556
0
    }
557
558
    RangeAccrualLeg& RangeAccrualLeg::withFixingDays(
559
0
                                     const std::vector<Natural>& fixingDays) {
560
0
        fixingDays_ = fixingDays;
561
0
        return *this;
562
0
    }
563
564
0
    RangeAccrualLeg& RangeAccrualLeg::withGearings(Real gearing) {
565
0
        gearings_ = std::vector<Real>(1,gearing);
566
0
        return *this;
567
0
    }
568
569
    RangeAccrualLeg& RangeAccrualLeg::withGearings(
570
0
                                          const std::vector<Real>& gearings) {
571
0
        gearings_ = gearings;
572
0
        return *this;
573
0
    }
574
575
0
    RangeAccrualLeg& RangeAccrualLeg::withSpreads(Spread spread) {
576
0
        spreads_ = std::vector<Spread>(1,spread);
577
0
        return *this;
578
0
    }
579
580
    RangeAccrualLeg& RangeAccrualLeg::withSpreads(
581
0
                                         const std::vector<Spread>& spreads) {
582
0
        spreads_ = spreads;
583
0
        return *this;
584
0
    }
585
586
0
    RangeAccrualLeg& RangeAccrualLeg::withLowerTriggers(Rate trigger) {
587
0
        lowerTriggers_ = std::vector<Rate>(1,trigger);
588
0
        return *this;
589
0
    }
590
591
    RangeAccrualLeg& RangeAccrualLeg::withLowerTriggers(
592
0
                                          const std::vector<Rate>& triggers) {
593
0
        lowerTriggers_ = triggers;
594
0
        return *this;
595
0
    }
596
597
0
    RangeAccrualLeg& RangeAccrualLeg::withUpperTriggers(Rate trigger) {
598
0
        upperTriggers_ = std::vector<Rate>(1,trigger);
599
0
        return *this;
600
0
    }
601
602
    RangeAccrualLeg& RangeAccrualLeg::withUpperTriggers(
603
0
                                          const std::vector<Rate>& triggers) {
604
0
        upperTriggers_ = triggers;
605
0
        return *this;
606
0
    }
607
608
    RangeAccrualLeg& RangeAccrualLeg::withObservationTenor(
609
0
                                                        const Period& tenor) {
610
0
        observationTenor_ = tenor;
611
0
        return *this;
612
0
    }
613
614
    RangeAccrualLeg& RangeAccrualLeg::withObservationConvention(
615
0
                                           BusinessDayConvention convention) {
616
0
        observationConvention_ = convention;
617
0
        return *this;
618
0
    }
619
620
0
    RangeAccrualLeg::operator Leg() const {
621
622
0
        QL_REQUIRE(!notionals_.empty(), "no notional given");
623
624
0
        Size n = schedule_.size()-1;
625
0
        QL_REQUIRE(notionals_.size() <= n,
626
0
                   "too many nominals (" << notionals_.size() <<
627
0
                   "), only " << n << " required");
628
0
        QL_REQUIRE(fixingDays_.size() <= n,
629
0
                   "too many fixingDays (" << fixingDays_.size() <<
630
0
                   "), only " << n << " required");
631
0
        QL_REQUIRE(gearings_.size()<=n,
632
0
                   "too many gearings (" << gearings_.size() <<
633
0
                   "), only " << n << " required");
634
0
        QL_REQUIRE(spreads_.size()<=n,
635
0
                   "too many spreads (" << spreads_.size() <<
636
0
                   "), only " << n << " required");
637
0
        QL_REQUIRE(lowerTriggers_.size()<=n,
638
0
                   "too many lowerTriggers (" << lowerTriggers_.size() <<
639
0
                   "), only " << n << " required");
640
0
        QL_REQUIRE(upperTriggers_.size()<=n,
641
0
                   "too many upperTriggers (" << upperTriggers_.size() <<
642
0
                   "), only " << n << " required");
643
644
0
        Leg leg(n);
645
646
        // the following is not always correct
647
0
        Calendar calendar = schedule_.calendar();
648
649
0
        Date refStart, start, refEnd, end;
650
0
        Date paymentDate;
651
652
0
        for (Size i=0; i<n; ++i) {
653
0
            refStart = start = schedule_.date(i);
654
0
            refEnd   =   end = schedule_.date(i+1);
655
0
            paymentDate = calendar.adjust(end, paymentAdjustment_);
656
0
            if (i==0 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) {
657
0
                BusinessDayConvention bdc = schedule_.businessDayConvention();
658
0
                refStart = calendar.adjust(end - schedule_.tenor(), bdc);
659
0
            }
660
0
            if (i==n-1 && schedule_.hasIsRegular() && !schedule_.isRegular(i+1)) {
661
0
                BusinessDayConvention bdc = schedule_.businessDayConvention();
662
0
                refEnd = calendar.adjust(start + schedule_.tenor(), bdc);
663
0
            }
664
0
            if (detail::get(gearings_, i, 1.0) == 0.0) { // fixed coupon
665
0
                leg.push_back(ext::shared_ptr<CashFlow>(new
666
0
                    FixedRateCoupon(paymentDate,
667
0
                                    detail::get(notionals_, i, Null<Real>()),
668
0
                                    detail::get(spreads_, i, 0.0),
669
0
                                    paymentDayCounter_,
670
0
                                    start, end, refStart, refEnd)));
671
0
            } else { // floating coupon
672
0
                auto observationSchedule =
673
0
                    Schedule(start, end,
674
0
                             observationTenor_, calendar,
675
0
                             observationConvention_,
676
0
                             observationConvention_,
677
0
                             DateGeneration::Forward, false);
678
679
0
                    leg.push_back(ext::shared_ptr<CashFlow>(new
680
0
                       RangeAccrualFloatersCoupon(
681
0
                            paymentDate,
682
0
                            detail::get(notionals_, i, Null<Real>()),
683
0
                            index_,
684
0
                            start, end,
685
0
                            detail::get(fixingDays_, i, 2),
686
0
                            paymentDayCounter_,
687
0
                            detail::get(gearings_, i, 1.0),
688
0
                            detail::get(spreads_, i, 0.0),
689
0
                            refStart, refEnd,
690
0
                            observationSchedule,
691
0
                            detail::get(lowerTriggers_, i, Null<Rate>()),
692
0
                            detail::get(upperTriggers_, i, Null<Rate>()))));
693
0
            }
694
0
        }
695
0
        return leg;
696
0
    }
697
698
}