Coverage Report

Created: 2025-11-04 06:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/credit/riskyassetswap.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008, 2009 Roland Lichters
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <https://www.quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
#include <ql/event.hpp>
21
#include <ql/experimental/credit/riskyassetswap.hpp>
22
#include <ql/utilities/null_deleter.hpp>
23
#include <utility>
24
25
namespace QuantLib {
26
27
    RiskyAssetSwap::RiskyAssetSwap(bool fixedPayer,
28
                                   Real nominal,
29
                                   Schedule fixedSchedule,
30
                                   Schedule floatSchedule,
31
                                   DayCounter fixedDayCounter,
32
                                   DayCounter floatDayCounter,
33
                                   Rate spread,
34
                                   Rate recoveryRate,
35
                                   Handle<YieldTermStructure> yieldTS,
36
                                   Handle<DefaultProbabilityTermStructure> defaultTS,
37
                                   Rate coupon)
38
0
    : fixedPayer_(fixedPayer), nominal_(nominal), fixedSchedule_(std::move(fixedSchedule)),
39
0
      floatSchedule_(std::move(floatSchedule)), fixedDayCounter_(std::move(fixedDayCounter)),
40
0
      floatDayCounter_(std::move(floatDayCounter)), spread_(spread), recoveryRate_(recoveryRate),
41
0
      yieldTS_(std::move(yieldTS)), defaultTS_(std::move(defaultTS)), coupon_(coupon) {
42
43
0
        registerWith (yieldTS_);
44
0
        registerWith (defaultTS_);
45
0
    }
Unexecuted instantiation: QuantLib::RiskyAssetSwap::RiskyAssetSwap(bool, double, QuantLib::Schedule, QuantLib::Schedule, QuantLib::DayCounter, QuantLib::DayCounter, double, double, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure>, double)
Unexecuted instantiation: QuantLib::RiskyAssetSwap::RiskyAssetSwap(bool, double, QuantLib::Schedule, QuantLib::Schedule, QuantLib::DayCounter, QuantLib::DayCounter, double, double, QuantLib::Handle<QuantLib::YieldTermStructure>, QuantLib::Handle<QuantLib::DefaultProbabilityTermStructure>, double)
46
47
0
    bool RiskyAssetSwap::isExpired () const {
48
0
        return detail::simple_event(fixedSchedule_.dates().back())
49
0
               .hasOccurred(yieldTS_->referenceDate());
50
0
    }
51
52
53
0
    void RiskyAssetSwap::setupExpired() const {
54
0
        Instrument::setupExpired();
55
0
    }
56
57
58
0
    void RiskyAssetSwap::performCalculations() const {
59
        // order of calls is essential
60
0
        floatAnnuity_   = floatAnnuity();
61
0
        fixedAnnuity_   = fixedAnnuity();
62
0
        parCoupon_      = parCoupon();
63
64
0
        if (coupon_ == Null<Rate>())  coupon_ = parCoupon_;
65
66
0
        recoveryValue_  = recoveryValue();
67
0
        riskyBondPrice_ = riskyBondPrice();
68
69
0
        NPV_ = riskyBondPrice_
70
0
            - coupon_ * fixedAnnuity_
71
0
            + yieldTS_->discount (fixedSchedule_.dates().front())
72
0
            - yieldTS_->discount (fixedSchedule_.dates().back())
73
0
            + spread_ * floatAnnuity_;
74
75
0
        NPV_ *= nominal_;
76
77
0
        if (!fixedPayer_)
78
0
            NPV_ *= -1;
79
0
    }
80
81
82
0
    Real RiskyAssetSwap::floatAnnuity () const {
83
0
        Real annuity = 0;
84
0
        for (Size i = 1; i < floatSchedule_.size(); i++) {
85
0
            Time dcf = floatDayCounter_.yearFraction (floatSchedule_[i-1],
86
0
                                                      floatSchedule_[i]);
87
0
            annuity += dcf * yieldTS_->discount (floatSchedule_[i]);
88
0
        }
89
0
        return annuity;
90
0
    }
91
92
93
0
    Real RiskyAssetSwap::fixedAnnuity () const {
94
0
        Real annuity = 0;
95
0
        for (Size i = 1; i < floatSchedule_.size(); i++) {
96
0
            Time dcf = fixedDayCounter_.yearFraction (floatSchedule_[i-1],
97
0
                                                      floatSchedule_[i]);
98
0
            annuity += dcf * yieldTS_->discount (floatSchedule_[i]);
99
0
        }
100
0
        return annuity;
101
0
    }
102
103
104
0
    Real RiskyAssetSwap::parCoupon () const {
105
0
        return (yieldTS_->discount(fixedSchedule_.dates().front())
106
0
                -yieldTS_->discount(fixedSchedule_.dates().back()))
107
0
            / fixedAnnuity_;
108
0
    }
109
110
111
0
    Real RiskyAssetSwap::recoveryValue() const {
112
0
        Real recoveryValue = 0;
113
        // simple Euler integral to evaluate the recovery value
114
0
        for (Size i = 1; i < fixedSchedule_.size(); i++) {
115
0
            TimeUnit stepSize = Days;
116
0
            Date d;
117
0
            if (fixedSchedule_[i-1] >= defaultTS_->referenceDate())
118
0
                d = fixedSchedule_[i-1];
119
0
            else
120
0
                d = defaultTS_->referenceDate();
121
0
            Date d0 = d;
122
0
            do {
123
0
                Real disc = yieldTS_->discount (d);
124
0
                Real dd   = defaultTS_->defaultDensity (d, true);
125
0
                Real dcf  = defaultTS_->dayCounter().yearFraction (d0, d);
126
127
0
                recoveryValue  += disc * dd * dcf;
128
129
0
                d0 = d;
130
131
0
                d = NullCalendar().advance (d0, 1, stepSize, Unadjusted);
132
0
            }
133
0
            while (d < fixedSchedule_[i]);
134
0
        }
135
0
        recoveryValue *= recoveryRate_;
136
137
0
        return recoveryValue;
138
0
    }
139
140
141
0
    Real RiskyAssetSwap::riskyBondPrice () const {
142
0
        Real value = 0;
143
0
        for (Size i = 1; i < fixedSchedule_.size(); i++) {
144
0
            Time dcf = fixedDayCounter_.yearFraction (fixedSchedule_[i-1],
145
0
                                                      fixedSchedule_[i]);
146
0
            value += dcf * yieldTS_->discount (fixedSchedule_[i])
147
0
                * defaultTS_->survivalProbability (fixedSchedule_[i], true);
148
0
        }
149
0
        value *= coupon_;
150
151
0
        value += yieldTS_->discount (fixedSchedule_.dates().back())
152
0
            * defaultTS_->survivalProbability (fixedSchedule_.dates().back(),
153
0
                                               true);
154
155
0
        return value + recoveryValue_;
156
0
    }
157
158
159
0
    Real RiskyAssetSwap::fairSpread () {
160
0
        calculate();
161
162
0
        Real value = 0;
163
0
        for (Size i = 1; i < fixedSchedule_.size(); i++) {
164
0
            Time dcf = fixedDayCounter_.yearFraction (fixedSchedule_[i-1],
165
0
                                                      fixedSchedule_[i]);
166
0
            value += dcf * yieldTS_->discount (fixedSchedule_[i])
167
0
                * defaultTS_->defaultProbability (fixedSchedule_[i], true);
168
0
        }
169
0
        value *= coupon_;
170
171
0
        value += yieldTS_->discount (fixedSchedule_.dates().back())
172
0
            * defaultTS_->defaultProbability (fixedSchedule_.dates().back(),
173
0
                                              true);
174
175
0
        Real initialDiscount = yieldTS_->discount(fixedSchedule_[0]);
176
177
0
        return (1.0 - initialDiscount + value - recoveryValue_) / fixedAnnuity_;
178
0
    }
179
180
181
    AssetSwapHelper::AssetSwapHelper(const Handle<Quote>& spread,
182
                                     const Period& tenor,
183
                                     Natural settlementDays,
184
                                     Calendar calendar,
185
                                     const Period& fixedPeriod,
186
                                     BusinessDayConvention fixedConvention,
187
                                     DayCounter fixedDayCount,
188
                                     const Period& floatPeriod,
189
                                     BusinessDayConvention floatConvention,
190
                                     DayCounter floatDayCount,
191
                                     Real recoveryRate,
192
                                     const RelinkableHandle<YieldTermStructure>& yieldTS,
193
                                     const Period& integrationStepSize)
194
0
    : DefaultProbabilityHelper(spread), tenor_(tenor), settlementDays_(settlementDays),
195
0
      calendar_(std::move(calendar)), fixedConvention_(fixedConvention), fixedPeriod_(fixedPeriod),
196
0
      fixedDayCount_(std::move(fixedDayCount)), floatConvention_(floatConvention),
197
0
      floatPeriod_(floatPeriod), floatDayCount_(std::move(floatDayCount)),
198
0
      recoveryRate_(recoveryRate), yieldTS_(yieldTS), integrationStepSize_(integrationStepSize) {
199
200
0
        initializeDates();
201
202
0
        registerWith(Settings::instance().evaluationDate());
203
0
        registerWith(yieldTS);
204
0
    }
205
206
0
    Real AssetSwapHelper::impliedQuote() const {
207
0
        QL_REQUIRE(!probability_.empty(),
208
0
                   "default term structure not set");
209
        // we didn't register as observers - force calculation
210
0
        asw_->recalculate();
211
0
        return asw_->fairSpread();
212
0
    }
213
214
    void AssetSwapHelper::setTermStructure(
215
0
                                        DefaultProbabilityTermStructure* ts) {
216
0
        DefaultProbabilityHelper::setTermStructure(ts);
217
218
0
        probability_.linkTo(
219
0
            ext::shared_ptr<DefaultProbabilityTermStructure>(ts, null_deleter()),
220
0
            false);
221
222
0
        initializeDates();
223
0
    }
224
225
0
    void AssetSwapHelper::update() {
226
0
        if (evaluationDate_ != Settings::instance().evaluationDate())
227
0
            initializeDates();
228
229
0
        DefaultProbabilityHelper::update();
230
0
    }
231
232
0
    void AssetSwapHelper::initializeDates() {
233
0
        evaluationDate_ = Settings::instance().evaluationDate();
234
235
0
        earliestDate_ = calendar_.advance (evaluationDate_,
236
0
                                           settlementDays_, Days);
237
238
0
        Date maturity = earliestDate_ + tenor_;
239
240
0
        latestDate_ = calendar_.adjust (maturity, fixedConvention_);
241
242
0
        Schedule fixedSchedule(earliestDate_, maturity,
243
0
                               fixedPeriod_, calendar_,
244
0
                               fixedConvention_, fixedConvention_,
245
0
                               DateGeneration::Forward, false);
246
0
        Schedule floatSchedule(earliestDate_, maturity,
247
0
                               floatPeriod_, calendar_,
248
0
                               floatConvention_, floatConvention_,
249
0
                               DateGeneration::Forward, false);
250
251
0
        asw_ = ext::make_shared<RiskyAssetSwap>(true,
252
0
                                                         100.0,
253
0
                                                         fixedSchedule,
254
0
                                                         floatSchedule,
255
0
                                                         fixedDayCount_,
256
0
                                                         floatDayCount_,
257
0
                                                         0.01,
258
0
                                                         recoveryRate_,
259
0
                                                         yieldTS_,
260
0
                                                         probability_);
261
0
    }
262
263
}