Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/cashflows/digitalcoupon.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2007 Cristina Duminuco
5
 Copyright (C) 2007 Giorgio Facchinetti
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
 <http://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/cashflows/capflooredcoupon.hpp>
22
#include <ql/cashflows/digitalcoupon.hpp>
23
#include <ql/indexes/indexmanager.hpp>
24
#include <ql/indexes/interestrateindex.hpp>
25
26
namespace QuantLib {
27
28
    DigitalCoupon::DigitalCoupon(const ext::shared_ptr<FloatingRateCoupon>& underlying,
29
                                 Rate callStrike,
30
                                 Position::Type callPosition,
31
                                 bool isCallATMIncluded,
32
                                 Rate callDigitalPayoff,
33
                                 Rate putStrike,
34
                                 Position::Type putPosition,
35
                                 bool isPutATMIncluded,
36
                                 Rate putDigitalPayoff,
37
                                 ext::shared_ptr<DigitalReplication> replication,
38
                                 const bool nakedOption)
39
0
    : FloatingRateCoupon(underlying->date(),
40
0
                         underlying->nominal(),
41
0
                         underlying->accrualStartDate(),
42
0
                         underlying->accrualEndDate(),
43
0
                         underlying->fixingDays(),
44
0
                         underlying->index(),
45
0
                         underlying->gearing(),
46
0
                         underlying->spread(),
47
0
                         underlying->referencePeriodStart(),
48
0
                         underlying->referencePeriodEnd(),
49
0
                         underlying->dayCounter(),
50
0
                         underlying->isInArrears()),
51
0
      underlying_(underlying), isCallATMIncluded_(isCallATMIncluded),
52
0
      isPutATMIncluded_(isPutATMIncluded), nakedOption_(nakedOption) {
53
54
0
        if (replication == nullptr)
55
0
            replication = ext::make_shared<DigitalReplication>();
56
        
57
0
        QL_REQUIRE(replication->gap()>0.0, "Non positive epsilon not allowed");
58
59
0
        callLeftEps_ = callRightEps_ = putLeftEps_ = putRightEps_ = replication->gap() / 2;
60
0
        replicationType_ = replication->replicationType();
61
        
62
0
        if (putStrike == Null<Rate>()) {
63
0
            QL_REQUIRE(putDigitalPayoff == Null<Rate>(),
64
0
            "Put Cash rate non allowed if put strike is null");
65
0
        }
66
0
        if (callStrike == Null<Rate>()) {
67
0
            QL_REQUIRE(callDigitalPayoff == Null<Rate>(),
68
0
            "Call Cash rate non allowed if call strike is null");
69
0
        }
70
0
        if (callStrike != Null<Rate>()) {
71
0
            hasCallStrike_ = true;
72
0
            callStrike_ = callStrike;
73
0
            switch (callPosition) {
74
0
                case Position::Long :
75
0
                    callCsi_ = 1.0;
76
0
                    break;
77
0
                case Position::Short :
78
0
                    callCsi_ = -1.0;
79
0
                    break;
80
0
                default:
81
0
                    QL_FAIL("unsupported position type");
82
0
            }
83
0
            if (callDigitalPayoff != Null<Rate>()){
84
0
                callDigitalPayoff_ = callDigitalPayoff;
85
0
                isCallCashOrNothing_ = true;
86
0
            }
87
0
        }
88
0
        if (putStrike != Null<Rate>()){
89
0
            hasPutStrike_ = true;
90
0
            putStrike_ = putStrike;
91
0
            switch (putPosition) {
92
0
                case Position::Long :
93
0
                    putCsi_ = 1.0;
94
0
                    break;
95
0
                case Position::Short :
96
0
                    putCsi_ = -1.0;
97
0
                    break;
98
0
                default:
99
0
                    QL_FAIL("unsupported position type");
100
0
            }
101
0
            if (putDigitalPayoff != Null<Rate>()){
102
0
                putDigitalPayoff_ = putDigitalPayoff;
103
0
                isPutCashOrNothing_ = true;
104
0
            }
105
0
        }
106
107
0
        switch (replicationType_) {
108
0
          case Replication::Central :
109
            // do nothing
110
0
            break;
111
0
          case Replication::Sub :
112
0
            if (hasCallStrike_) {
113
0
                switch (callPosition) {
114
0
                    case Position::Long :
115
0
                        callLeftEps_ = 0.;
116
0
                        callRightEps_ = replication->gap();
117
0
                        break;
118
0
                    case Position::Short :
119
0
                        callLeftEps_ = replication->gap();
120
0
                        callRightEps_ = 0.;
121
0
                        break;
122
0
                    default:
123
0
                        QL_FAIL("unsupported position type");
124
0
                }
125
0
            }
126
0
            if (hasPutStrike_) {
127
0
                switch (putPosition) {
128
0
                    case Position::Long :
129
0
                        putLeftEps_ = replication->gap();
130
0
                        putRightEps_ = 0.;
131
0
                        break;
132
0
                    case Position::Short :
133
0
                        putLeftEps_ = 0.;
134
0
                        putRightEps_ = replication->gap();
135
0
                        break;
136
0
                    default:
137
0
                        QL_FAIL("unsupported position type");
138
0
                }
139
0
            }
140
0
            break;
141
0
          case Replication::Super :
142
0
            if (hasCallStrike_) {
143
0
                switch (callPosition) {
144
0
                    case Position::Long :
145
0
                        callLeftEps_ = replication->gap();
146
0
                        callRightEps_ = 0.;
147
0
                        break;
148
0
                    case Position::Short :
149
0
                        callLeftEps_ = 0.;
150
0
                        callRightEps_ = replication->gap();
151
0
                        break;
152
0
                    default:
153
0
                        QL_FAIL("unsupported position type");
154
0
                }
155
0
            }
156
0
            if (hasPutStrike_) {
157
0
                switch (putPosition) {
158
0
                    case Position::Long :
159
0
                        putLeftEps_ = 0.;
160
0
                        putRightEps_ = replication->gap();
161
0
                        break;
162
0
                    case Position::Short :
163
0
                        putLeftEps_ = replication->gap();
164
0
                        putRightEps_ = 0.;
165
0
                        break;
166
0
                    default:
167
0
                        QL_FAIL("unsupported position type");
168
0
                }
169
0
            }
170
0
            break;
171
0
          default:
172
0
            QL_FAIL("unsupported replication type");
173
0
        }
174
175
0
        registerWith(underlying);
176
0
    }
Unexecuted instantiation: QuantLib::DigitalCoupon::DigitalCoupon(boost::shared_ptr<QuantLib::FloatingRateCoupon> const&, double, QuantLib::Position::Type, bool, double, double, QuantLib::Position::Type, bool, double, boost::shared_ptr<QuantLib::DigitalReplication>, bool)
Unexecuted instantiation: QuantLib::DigitalCoupon::DigitalCoupon(boost::shared_ptr<QuantLib::FloatingRateCoupon> const&, double, QuantLib::Position::Type, bool, double, double, QuantLib::Position::Type, bool, double, boost::shared_ptr<QuantLib::DigitalReplication>, bool)
177
178
179
0
    Rate DigitalCoupon::callOptionRate() const {
180
181
0
        Rate callOptionRate = Rate(0.);
182
0
        if(hasCallStrike_) {
183
            // Step function
184
0
            callOptionRate = isCallCashOrNothing_ ? callDigitalPayoff_ : callStrike_;
185
0
            CappedFlooredCoupon next(underlying_, callStrike_ + callRightEps_);
186
0
            CappedFlooredCoupon previous(underlying_, callStrike_ - callLeftEps_);
187
0
            callOptionRate *= (next.rate() - previous.rate())
188
0
                            / (callLeftEps_ + callRightEps_);
189
0
            if (!isCallCashOrNothing_) {
190
                // Call
191
0
                CappedFlooredCoupon atStrike(underlying_, callStrike_);
192
0
                Rate call = underlying_->rate() - atStrike.rate();
193
                // Sum up
194
0
                callOptionRate += call;
195
0
            }
196
0
        }
197
0
        return callOptionRate;
198
0
    }
199
200
0
    Rate DigitalCoupon::putOptionRate() const {
201
202
0
        Rate putOptionRate = Rate(0.);
203
0
        if(hasPutStrike_) {
204
            // Step function
205
0
            putOptionRate = isPutCashOrNothing_ ? putDigitalPayoff_ : putStrike_;
206
0
            CappedFlooredCoupon next(underlying_, Null<Rate>(), putStrike_ + putRightEps_);
207
0
            CappedFlooredCoupon previous(underlying_, Null<Rate>(), putStrike_ - putLeftEps_);
208
0
            putOptionRate *= (next.rate() - previous.rate())
209
0
                           / (putLeftEps_ + putRightEps_);
210
0
            if (!isPutCashOrNothing_) {
211
                // Put
212
0
                CappedFlooredCoupon atStrike(underlying_, Null<Rate>(), putStrike_);
213
0
                Rate put = - underlying_->rate() + atStrike.rate();
214
                // Sum up
215
0
                putOptionRate -= put;
216
0
            }
217
0
        }
218
0
        return putOptionRate;
219
0
    }
220
221
0
    void DigitalCoupon::deepUpdate() {
222
0
        update();
223
0
        underlying_->deepUpdate();
224
0
    }
225
226
0
    void DigitalCoupon::performCalculations() const {
227
228
0
        QL_REQUIRE(underlying_->pricer(), "pricer not set");
229
230
0
        Date fixingDate = underlying_->fixingDate();
231
0
        Date today = Settings::instance().evaluationDate();
232
0
        bool enforceTodaysHistoricFixings =
233
0
            Settings::instance().enforcesTodaysHistoricFixings();
234
0
        Rate underlyingRate = nakedOption_ ? 0.0 : underlying_->rate();
235
0
        if (fixingDate < today ||
236
0
            ((fixingDate == today) && enforceTodaysHistoricFixings)) {
237
            // must have been fixed
238
0
            rate_ = underlyingRate + callCsi_ * callPayoff() + putCsi_  * putPayoff();
239
0
        } else if (fixingDate == today) {
240
            // might have been fixed
241
0
            if (underlying_->index()->hasHistoricalFixing(fixingDate)) {
242
0
                rate_ = underlyingRate + callCsi_ * callPayoff() + putCsi_  * putPayoff();
243
0
            } else {
244
0
                rate_ = underlyingRate + callCsi_ * callOptionRate() + putCsi_ * putOptionRate();
245
0
            }
246
0
        } else {
247
0
            rate_ = underlyingRate + callCsi_ * callOptionRate() + putCsi_ * putOptionRate();
248
0
        }
249
0
    }
250
251
0
    Rate DigitalCoupon::rate() const {
252
0
        calculate();
253
0
        return rate_;
254
0
    }
255
256
0
    Rate DigitalCoupon::convexityAdjustment() const {
257
0
        return underlying_->convexityAdjustment();
258
0
    }
259
260
0
    Rate DigitalCoupon::callStrike() const {
261
0
        if (hasCall())
262
0
            return callStrike_;
263
0
        else
264
0
            return Null<Rate>();
265
0
   }
266
267
0
    Rate DigitalCoupon::putStrike() const {
268
0
        if (hasPut())
269
0
            return putStrike_;
270
0
        else
271
0
            return Null<Rate>();
272
0
    }
273
274
0
    Rate DigitalCoupon::callDigitalPayoff() const {
275
0
        if (isCallCashOrNothing_)
276
0
            return callDigitalPayoff_;
277
0
        else
278
0
            return Null<Rate>();
279
0
    }
280
281
0
    Rate DigitalCoupon::putDigitalPayoff() const {
282
0
        if (isPutCashOrNothing_)
283
0
            return putDigitalPayoff_;
284
0
        else
285
0
            return Null<Rate>();
286
0
    }
287
288
0
    void DigitalCoupon::accept(AcyclicVisitor& v) {
289
0
        typedef FloatingRateCoupon super;
290
0
        auto* v1 = dynamic_cast<Visitor<DigitalCoupon>*>(&v);
291
0
        if (v1 != nullptr)
292
0
            v1->visit(*this);
293
0
        else
294
0
            super::accept(v);
295
0
    }
296
297
0
    Rate DigitalCoupon::callPayoff() const {
298
        // to use only if index has fixed
299
0
        Rate payoff(0.);
300
0
        if(hasCallStrike_) {
301
0
            Rate underlyingRate = underlying_->rate();
302
0
            if ( (underlyingRate - callStrike_) > 1.e-16 ) {
303
0
                payoff = isCallCashOrNothing_ ? callDigitalPayoff_ : underlyingRate;
304
0
            } else {
305
0
                if (isCallATMIncluded_) {
306
0
                    if ( std::abs(callStrike_ - underlyingRate) <= 1.e-16 )
307
0
                        payoff = isCallCashOrNothing_ ? callDigitalPayoff_ : underlyingRate;
308
0
                }
309
0
            }
310
0
        }
311
0
        return payoff;
312
0
    }
313
314
0
    Rate DigitalCoupon::putPayoff() const {
315
        // to use only if index has fixed
316
0
        Rate payoff(0.);
317
0
        if(hasPutStrike_) {
318
0
            Rate underlyingRate = underlying_->rate();
319
0
            if ( (putStrike_ - underlyingRate) > 1.e-16 ) {
320
0
                payoff = isPutCashOrNothing_ ? putDigitalPayoff_ : underlyingRate;
321
0
            } else {
322
                // putStrike_ <= underlyingRate
323
0
                if (isPutATMIncluded_) {
324
0
                    if ( std::abs(putStrike_ - underlyingRate) <= 1.e-16 )
325
0
                        payoff = isPutCashOrNothing_ ? putDigitalPayoff_ : underlyingRate;
326
0
                }
327
0
            }
328
0
        }
329
0
        return payoff;
330
0
    }
331
332
}
333