Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/pricingengines/swap/cvaswapengine.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) 2015 Jose Aparicio
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
 <http://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/pricingengines/swap/cvaswapengine.hpp>
21
#include <ql/cashflows/fixedratecoupon.hpp>
22
#include <ql/cashflows/floatingratecoupon.hpp>
23
#include <ql/indexes/iborindex.hpp>
24
#include <ql/instruments/makevanillaswap.hpp>
25
#include <ql/exercise.hpp>
26
#include <ql/pricingengines/swap/discountingswapengine.hpp>
27
#include <ql/termstructures/credit/flathazardrate.hpp>
28
#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
29
#include <ql/time/calendars/nullcalendar.hpp>
30
31
namespace QuantLib {
32
  
33
  CounterpartyAdjSwapEngine::CounterpartyAdjSwapEngine(
34
      const Handle<YieldTermStructure>& discountCurve,
35
      const Handle<PricingEngine>& swaptionEngine,
36
      const Handle<DefaultProbabilityTermStructure>& ctptyDTS,
37
      Real ctptyRecoveryRate,
38
      const Handle<DefaultProbabilityTermStructure>& invstDTS,
39
      Real invstRecoveryRate)
40
0
  : baseSwapEngine_(Handle<PricingEngine>(
41
0
      ext::make_shared<DiscountingSwapEngine>(discountCurve))),
42
0
    swaptionletEngine_(swaptionEngine),
43
0
    discountCurve_(discountCurve),
44
0
    defaultTS_(ctptyDTS), 
45
0
    ctptyRecoveryRate_(ctptyRecoveryRate),
46
0
    invstDTS_(invstDTS.empty() ? Handle<DefaultProbabilityTermStructure>(
47
0
        ext::make_shared<FlatHazardRate>(0, NullCalendar(), 1.e-12, 
48
0
        ctptyDTS->dayCounter()) ) : invstDTS ),
49
0
    invstRecoveryRate_(invstRecoveryRate)
50
0
  {
51
0
      registerWith(discountCurve);
52
0
      registerWith(ctptyDTS);
53
0
      registerWith(invstDTS_);
54
0
      registerWith(swaptionEngine);
55
0
  }
56
57
    CounterpartyAdjSwapEngine::CounterpartyAdjSwapEngine(
58
        const Handle<YieldTermStructure>& discountCurve,
59
        const Volatility blackVol,
60
        const Handle<DefaultProbabilityTermStructure>& ctptyDTS,
61
        Real ctptyRecoveryRate,
62
        const Handle<DefaultProbabilityTermStructure>& invstDTS,
63
        Real invstRecoveryRate)
64
0
  : baseSwapEngine_(Handle<PricingEngine>(
65
0
      ext::make_shared<DiscountingSwapEngine>(discountCurve))),
66
0
    swaptionletEngine_(Handle<PricingEngine>(
67
0
      ext::make_shared<BlackSwaptionEngine>(discountCurve,
68
0
        blackVol))),
69
0
    discountCurve_(discountCurve),
70
0
    defaultTS_(ctptyDTS), 
71
0
    ctptyRecoveryRate_(ctptyRecoveryRate),
72
0
    invstDTS_(invstDTS.empty() ? Handle<DefaultProbabilityTermStructure>(
73
0
        ext::make_shared<FlatHazardRate>(0, NullCalendar(), 1.e-12, 
74
0
        ctptyDTS->dayCounter()) ) : invstDTS ),
75
0
    invstRecoveryRate_(invstRecoveryRate)
76
0
  {
77
0
      registerWith(discountCurve);
78
0
      registerWith(ctptyDTS);
79
0
      registerWith(invstDTS_);
80
0
  }
81
82
  CounterpartyAdjSwapEngine::CounterpartyAdjSwapEngine(
83
        const Handle<YieldTermStructure>& discountCurve,
84
        const Handle<Quote>& blackVol,
85
        const Handle<DefaultProbabilityTermStructure>& ctptyDTS,
86
        Real ctptyRecoveryRate,
87
        const Handle<DefaultProbabilityTermStructure>& invstDTS,
88
        Real invstRecoveryRate)
89
0
  : baseSwapEngine_(Handle<PricingEngine>(
90
0
      ext::make_shared<DiscountingSwapEngine>(discountCurve))),
91
0
    swaptionletEngine_(Handle<PricingEngine>(
92
0
      ext::make_shared<BlackSwaptionEngine>(discountCurve,
93
0
        blackVol))),
94
0
    discountCurve_(discountCurve),
95
0
    defaultTS_(ctptyDTS), 
96
0
    ctptyRecoveryRate_(ctptyRecoveryRate),
97
0
    invstDTS_(invstDTS.empty() ? Handle<DefaultProbabilityTermStructure>(
98
0
        ext::make_shared<FlatHazardRate>(0, NullCalendar(), 1.e-12, 
99
0
        ctptyDTS->dayCounter()) ) : invstDTS ),
100
0
    invstRecoveryRate_(invstRecoveryRate)
101
0
  {
102
0
      registerWith(discountCurve);
103
0
      registerWith(ctptyDTS);
104
0
      registerWith(invstDTS_);
105
0
      registerWith(blackVol);
106
0
  }
107
108
0
  void CounterpartyAdjSwapEngine::calculate() const {
109
      /* both DTS, YTS ref dates and pricing date consistency 
110
         checks? settlement... */
111
0
    QL_REQUIRE(!discountCurve_.empty(),
112
0
                 "no discount term structure set");
113
0
    QL_REQUIRE(!defaultTS_.empty(),
114
0
                 "no ctpty default term structure set");
115
0
    QL_REQUIRE(!swaptionletEngine_.empty(),
116
0
                 "no swap option engine set");
117
118
0
    QL_REQUIRE(arguments_.nominal != Null<Real>(),
119
0
               "non-constant nominals are not supported yet");
120
121
0
    Date priceDate = defaultTS_->referenceDate();
122
123
0
    Real cumOptVal = 0., 
124
0
        cumPutVal = 0.;
125
    // Vanilla swap so 0 leg is floater
126
127
0
    auto nextFD = 
128
0
      arguments_.fixedPayDates.begin();
129
0
    Date swapletStart = priceDate;
130
0
    while (*nextFD < priceDate) ++nextFD;
131
132
    // Compute fair spread for strike value:
133
    // copy args into the non risky engine
134
0
    auto* noCVAArgs = dynamic_cast<Swap::arguments*>(baseSwapEngine_->getArguments());
135
0
    QL_REQUIRE(noCVAArgs != nullptr, "wrong argument type");
136
137
0
    noCVAArgs->legs = this->arguments_.legs;
138
0
    noCVAArgs->payer = this->arguments_.payer;
139
140
0
    baseSwapEngine_->calculate();
141
142
0
    ext::shared_ptr<FixedRateCoupon> coupon = ext::dynamic_pointer_cast<FixedRateCoupon>(arguments_.legs[0][0]);
143
0
    QL_REQUIRE(coupon,"dynamic cast of fixed leg coupon failed.");
144
0
    Rate baseSwapRate = coupon->rate();
145
146
0
    const auto* vSResults = dynamic_cast<const Swap::results*>(baseSwapEngine_->getResults());
147
0
    QL_REQUIRE(vSResults != nullptr, "wrong result type");
148
149
0
    Rate baseSwapFairRate = -baseSwapRate * vSResults->legNPV[1] / 
150
0
        vSResults->legNPV[0];
151
0
    Real baseSwapNPV = vSResults->value;
152
153
0
    Swap::Type reversedType = arguments_.type == Swap::Payer ? Swap::Receiver : Swap::Payer;
154
155
    // Swaplet options summatory:
156
0
    while(nextFD != arguments_.fixedPayDates.end()) {
157
      // iFD coupon not fixed, create swaptionlet:
158
0
      ext::shared_ptr<FloatingRateCoupon> floatCoupon = ext::dynamic_pointer_cast<FloatingRateCoupon>(arguments_.legs[1][0]);
159
0
      QL_REQUIRE(floatCoupon,"dynamic cast of floating leg coupon failed.");
160
0
      ext::shared_ptr<IborIndex> swapIndex = ext::dynamic_pointer_cast<IborIndex>(floatCoupon->index());
161
0
      QL_REQUIRE(swapIndex,"dynamic cast of floating leg index failed.");
162
163
      // Alternatively one could cap this period to, say, 1M 
164
      // Period swapPeriod = ext::dynamic_pointer_cast<FloatingRateCoupon>(
165
      //   arguments_.legs[1][0])->index()->tenor();
166
167
0
      Period baseSwapsTenor(arguments_.fixedPayDates.back().serialNumber() 
168
0
      - swapletStart.serialNumber(), Days);
169
0
      ext::shared_ptr<VanillaSwap> swaplet = MakeVanillaSwap(
170
0
        baseSwapsTenor,
171
0
        swapIndex, 
172
0
        baseSwapFairRate // strike
173
0
        )
174
0
      .withType(arguments_.type)
175
0
      .withNominal(arguments_.nominal)
176
          ////////      .withSettlementDays(2)
177
0
        .withEffectiveDate(swapletStart)
178
0
        .withTerminationDate(arguments_.fixedPayDates.back());
179
0
      ext::shared_ptr<VanillaSwap> revSwaplet = MakeVanillaSwap(
180
0
        baseSwapsTenor,
181
0
        swapIndex, 
182
0
        baseSwapFairRate // strike
183
0
        )
184
0
      .withType(reversedType)
185
0
      .withNominal(arguments_.nominal)
186
          /////////     .withSettlementDays(2)
187
0
        .withEffectiveDate(swapletStart)
188
0
        .withTerminationDate(arguments_.fixedPayDates.back());
189
190
0
      Swaption swaptionlet(swaplet, 
191
0
        ext::make_shared<EuropeanExercise>(swapletStart));
192
0
      Swaption putSwaplet(revSwaplet, 
193
0
        ext::make_shared<EuropeanExercise>(swapletStart));
194
0
      swaptionlet.setPricingEngine(swaptionletEngine_.currentLink());
195
0
      putSwaplet.setPricingEngine(swaptionletEngine_.currentLink());
196
197
      // atm underlying swap means that the value of put = value
198
      // call so this double pricing is not needed
199
0
      cumOptVal += swaptionlet.NPV() * defaultTS_->defaultProbability(
200
0
          swapletStart, *nextFD);
201
0
      cumPutVal += putSwaplet.NPV()  * invstDTS_->defaultProbability(
202
0
        swapletStart, *nextFD);
203
204
0
      swapletStart = *nextFD;
205
0
      ++nextFD;
206
0
    }
207
  
208
0
    results_.value = baseSwapNPV - (1.-ctptyRecoveryRate_) * cumOptVal
209
0
        + (1.-invstRecoveryRate_) * cumPutVal;
210
211
0
    results_.fairRate =  -baseSwapRate * (vSResults->legNPV[1] 
212
0
        - (1.-ctptyRecoveryRate_) * cumOptVal + 
213
0
          (1.-invstRecoveryRate_) * cumPutVal )
214
0
      / vSResults->legNPV[0];
215
216
0
  }
217
218
219
}