Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
analyticxccyblackriskparticipationagreementengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
21
22#include <ql/cashflows/floatingratecoupon.hpp>
23#include <ql/cashflows/simplecashflow.hpp>
24#include <ql/instruments/swap.hpp>
25#include <ql/pricingengines/swap/discountingswapengine.hpp>
26#include <ql/pricingengines/vanilla/analyticeuropeanengine.hpp>
27#include <ql/quotes/compositequote.hpp>
28
29namespace ore {
30namespace data {
31
33 const std::string& baseCcy, const std::map<std::string, Handle<YieldTermStructure>>& discountCurves,
34 const std::map<std::string, Handle<Quote>>& fxSpots, const Handle<DefaultProbabilityTermStructure>& defaultCurve,
35 const Handle<Quote>& recoveryRate, const Handle<BlackVolTermStructure>& volatility,
36 const bool alwaysRecomputeOptionRepresentation, const Size maxGapDays, const Size maxDiscretisationPoints)
37 : RiskParticipationAgreementBaseEngine(baseCcy, discountCurves, fxSpots, defaultCurve, recoveryRate, maxGapDays,
38 maxDiscretisationPoints),
39 volatility_(volatility), alwaysRecomputeOptionRepresentation_(alwaysRecomputeOptionRepresentation) {
40 registerWith(volatility_);
41}
42
44
45 QL_REQUIRE(arguments_.exercise == nullptr,
46 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): callability is not supported");
47
48 QL_REQUIRE(!volatility_.empty(),
49 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty discount curve");
50
51 std::string domCcy = arguments_.underlyingCcys[0];
52 std::string forCcy = domCcy;
53
54 for (auto const& ccy : arguments_.underlyingCcys) {
55 // take any ccy != domCcy as the for ccy, the matcher will check if all underlying legs are in dom or for ccy
56 if (ccy != domCcy)
57 forCcy = ccy;
58 }
59
60 QL_REQUIRE(!discountCurves_[domCcy].empty(),
61 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty discount curve for ccy "
62 << domCcy);
63 QL_REQUIRE(!discountCurves_[forCcy].empty(),
64 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty discount curve for ccy "
65 << forCcy);
66 QL_REQUIRE(
67 !fxSpots_[domCcy].empty(),
68 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty discount curve for ccy pair "
69 << domCcy + baseCcy_);
70 QL_REQUIRE(
71 !fxSpots_[forCcy].empty(),
72 "AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty discount curve for ccy pair "
73 << forCcy + baseCcy_);
74
75 struct divide {
76 Real operator()(Real x, Real y) const { return x / y; }
77 };
78
79 Handle<Quote> fxSpot(QuantLib::ext::make_shared<CompositeQuote<divide>>(fxSpots_[forCcy], fxSpots_[domCcy], divide()));
80
81 // check if we can reuse the fx option representation, otherwise compute it
82
83 if (alwaysRecomputeOptionRepresentation_ || arguments_.optionRepresentationReferenceDate == Date() ||
84 referenceDate_ != arguments_.optionRepresentationReferenceDate) {
85
86 results_.optionRepresentationReferenceDate = referenceDate_;
87 results_.optionRepresentationPeriods.clear();
88 results_.optionRepresentation.clear();
89
90 /* we construct one fx option per floating rate coupon on the midpoint of the accrual period
91 but only keep those with an underlying length of at least 1M */
92
93 for (Size i = 0; i < gridDates_.size() - 1; ++i) {
94 Date start = gridDates_[i];
95 Date end = gridDates_[i + 1];
96 Date mid = start + (end - start) / 2;
97 // mid might be = reference date degenerate cases where the first two discretisation points
98 // are only one day apart from each other
99 if (mid + 1 * Months <= arguments_.underlyingMaturity && mid > discountCurves_[baseCcy_]->referenceDate())
100 results_.optionRepresentationPeriods.push_back(std::make_tuple(mid, start, end));
101 }
102
103 for (auto const& e : results_.optionRepresentationPeriods) {
104 Date d = std::get<0>(e);
105 QuantExt::RepresentativeFxOptionMatcher matcher(arguments_.underlying, arguments_.underlyingPayer,
106 arguments_.underlyingCcys, std::get<0>(e), forCcy, domCcy,
107 discountCurves_[forCcy], discountCurves_[domCcy], fxSpot);
108 bool instrumentSet = false;
109 if (!close_enough(matcher.amount1(), 0.0) && forCcy != domCcy) {
110 Real strike = -matcher.amount2() / matcher.amount1();
111 if (strike > 0.0 && !close_enough(strike, 0.0)) {
112 // the amounts correspond to an actual FX Option
113 Option::Type type = matcher.amount1() > 0.0 ? Option::Call : Option::Put;
114 results_.optionRepresentation.push_back(QuantLib::ext::make_shared<VanillaOption>(
115 QuantLib::ext::make_shared<PlainVanillaPayoff>(type, strike), QuantLib::ext::make_shared<EuropeanExercise>(d)));
116 results_.optionMultiplier.push_back(std::abs(matcher.amount1()));
117 instrumentSet = true;
118 }
119 }
120 if (!instrumentSet) {
121 // otherwise set up a trivial instrument representing the positive part of the amount in dom ccy
122 Real amount = std::max(0.0, matcher.amount1() * fxSpot->value() / discountCurves_[domCcy]->discount(d) *
123 discountCurves_[forCcy]->discount(d) +
124 matcher.amount2());
125 results_.optionRepresentation.push_back(QuantLib::ext::make_shared<Swap>(
126 std::vector<Leg>{{QuantLib::ext::make_shared<SimpleCashFlow>(amount, d)}}, std::vector<bool>{false}));
127 results_.optionMultiplier.push_back(1.0);
128 }
129 }
130
131 } else {
132 results_.optionRepresentationReferenceDate = arguments_.optionRepresentationReferenceDate;
133 results_.optionRepresentationPeriods = arguments_.optionRepresentationPeriods;
134 results_.optionRepresentation = arguments_.optionRepresentation;
135 results_.optionMultiplier = arguments_.optionMultiplier;
136 QL_REQUIRE(results_.optionRepresentation.size() == results_.optionRepresentationPeriods.size(),
137 "AnalyticXCcyBlackRiskParticipationAgreementEngine::calculate(): inconsistent option representation "
138 "periods");
139 QL_REQUIRE(results_.optionRepresentation.size() == results_.optionMultiplier.size(),
140 "AnalyticXCcyBlackRiskParticipationAgreementEngine::calculate(): inconsistent option multiplier");
141 }
142
143 // attach an engine to the representative option (or swap)
144
145 auto swapEngine = QuantLib::ext::make_shared<DiscountingSwapEngine>(discountCurves_[domCcy]);
146 auto optionEngine = QuantLib::ext::make_shared<AnalyticEuropeanEngine>(QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
147 fxSpot, discountCurves_[forCcy], discountCurves_[domCcy], volatility_));
148
149 for (auto s : results_.optionRepresentation) {
150 if (auto tmp = QuantLib::ext::dynamic_pointer_cast<VanillaOption>(s))
151 s->setPricingEngine(optionEngine);
152 else if (auto tmp = QuantLib::ext::dynamic_pointer_cast<Swap>(s))
153 s->setPricingEngine(swapEngine);
154 else {
155 QL_FAIL("AnalyticXCcyBlackRiskParticipationAgreementEngine::protectionLegNpv(): internal error, could not "
156 "cast representative instrument to either VanillaOption or Swap");
157 }
158 }
159
160 // compute a CVA using the representative options
161
162 QL_REQUIRE(!fxSpots_[domCcy].empty(),
163 "AnalyticBlackRiskParticipationAgreementEngine::protectionLegNpv(): empty fx spot for ccy pair "
164 << domCcy + baseCcy_);
165
166 Real cva = 0.0;
167 std::vector<Real> optionPv(results_.optionRepresentationPeriods.size(), 0.0);
168 for (Size i = 0; i < results_.optionRepresentationPeriods.size(); ++i) {
169 Real pd = defaultCurve_->defaultProbability(std::get<1>(results_.optionRepresentationPeriods[i]),
170 std::get<2>(results_.optionRepresentationPeriods[i]));
171 Real swpNpv = results_.optionRepresentation[i]->NPV() * results_.optionMultiplier[i];
172 cva += pd * (1.0 - effectiveRecoveryRate_) * swpNpv * fxSpots_[domCcy]->value();
173 optionPv[i] = swpNpv;
174 }
175
176 // detach pricing engine from result swaption representation
177
178 QuantLib::ext::shared_ptr<PricingEngine> emptyEngine;
179 for (auto s : results_.optionRepresentation)
180 s->setPricingEngine(emptyEngine);
181
182 // set additional results
183
184 std::vector<Real> optionStrikes;
185 std::vector<Date> optionExerciseDates;
186 for (auto const& r : results_.optionRepresentation) {
187 if (auto o = QuantLib::ext::dynamic_pointer_cast<VanillaOption>(r)) {
188 if (auto p = QuantLib::ext::dynamic_pointer_cast<PlainVanillaPayoff>(o->payoff())) {
189 optionStrikes.push_back(p->strike());
190 } else {
191 optionStrikes.push_back(0.0);
192 }
193 if (!o->exercise()->dates().empty()) {
194 optionExerciseDates.push_back(o->exercise()->dates().front());
195 } else {
196 optionExerciseDates.push_back(Date());
197 }
198 } else if (auto s = QuantLib::ext::dynamic_pointer_cast<Swap>(r)) {
199 optionStrikes.push_back(0.0);
200 if (!s->leg(0).empty()) {
201 optionExerciseDates.push_back(s->leg(0).front()->date());
202 } else {
203 optionExerciseDates.push_back(Date());
204 }
205 }
206 }
207
208 results_.additionalResults["OptionNpvs"] = optionPv;
209 results_.additionalResults["FXSpot"] = fxSpots_[domCcy]->value();
210 results_.additionalResults["BaseCurrency"] = baseCcy_;
211 results_.additionalResults["DomesticCurrency"] = domCcy;
212 results_.additionalResults["ForeignCurrency"] = forCcy;
213 results_.additionalResults["OptionMultiplier"] = results_.optionMultiplier;
214 results_.additionalResults["OptionStrikes"] = optionStrikes;
215 results_.additionalResults["OptionExerciseDates"] = optionExerciseDates;
216
217 // return result
218
219 return arguments_.participationRate * cva;
220}
221
222} // namespace data
223} // namespace ore
const Instrument::results * results_
AnalyticXCcyBlackRiskParticipationAgreementEngine(const std::string &baseCcy, const std::map< std::string, Handle< YieldTermStructure > > &discountCurves, const std::map< std::string, Handle< Quote > > &fxSpots, const Handle< DefaultProbabilityTermStructure > &defaultCurve, const Handle< Quote > &recoveryRate, const Handle< BlackVolTermStructure > &volatility, const bool alwaysRecomputeOptionRepresentation, const Size maxGapDays=Null< Size >(), const Size maxDiscretisationPoints=Null< Size >())
std::map< std::string, Handle< YieldTermStructure > > discountCurves_
SafeStack< ValueType > value
@ data
Definition: log.hpp:77
Date referenceDate
Definition: utilities.cpp:442
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Swap::arguments * arguments_