Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
numericlgmflexiswapengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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
20
21namespace QuantExt {
22
23namespace {
24// this op is not defined for QL arrays, but it's handy for the rollback below
25Array max(Array x, const Real b) {
26 std::transform(x.begin(), x.end(), x.begin(), [b](const Real u) { return std::max(u, b); });
27 return x;
28}
29Array max(Array x, const Array& y) {
30 QL_REQUIRE(x.size() == y.size(),
31 "max(Array,Array) requires arrays of equal size, got " << x.size() << " and " << y.size());
32 for (Size i = 0; i < x.size(); ++i) {
33 x[i] = std::max(x[i], y[i]);
34 }
35 return x;
36}
37} // namespace
38
39NumericLgmFlexiSwapEngineBase::NumericLgmFlexiSwapEngineBase(const QuantLib::ext::shared_ptr<LinearGaussMarkovModel>& model,
40 const Real sy, const Size ny, const Real sx, const Size nx,
41 const Handle<YieldTermStructure>& discountCurve,
42 const Method method, const Real singleSwaptionThreshold)
43 : LgmConvolutionSolver(model, sy, ny, sx, nx), discountCurve_(discountCurve), method_(method),
44 singleSwaptionThreshold_(singleSwaptionThreshold) {}
45
46Real NumericLgmFlexiSwapEngineBase::underlyingValue(const Real x, const Real t, const Date& d, const Size fltIndex,
47 const Size fixIndex, const Real fltPayTime,
48 const Real fixPayTime) const {
49 Real val = 0.0;
50 Real om = type == VanillaSwap::Payer ? -1.0 : 1.0;
51 if (fixIndex != Null<Size>()) {
52 if (!QuantLib::close_enough(fixedNominal[fixIndex], 0.0))
53 val = om * fixedCoupons[fixIndex] / fixedNominal[fixIndex] *
54 model()->reducedDiscountBond(t, fixPayTime, x, discountCurve_);
55 }
56 if (fltIndex != Null<Size>()) {
57 iborModelCurve_->move(d, x);
58 Real fixing = floatingGearings[fltIndex] * iborModelIndex_->fixing(d) + floatingSpreads[fltIndex];
59 if (cappedRate[fltIndex] != Null<Real>())
60 fixing = std::min(fixing, cappedRate[fltIndex]);
61 if (flooredRate[fltIndex] != Null<Real>())
62 fixing = std::max(fixing, flooredRate[fltIndex]);
63 val += -om * fixing * floatingAccrualTimes[fltIndex] *
64 model()->reducedDiscountBond(t, fltPayTime, x, discountCurve_);
65 }
66 return val;
67} // NumericLgmFlexiSwapEngineBase::underlyingValue()
68
69std::pair<Real, Real> NumericLgmFlexiSwapEngineBase::calculate() const {
70
71 Date today = model()->parametrization()->termStructure()->referenceDate();
72 Real phi = optionPosition == Position::Long ? 1.0 : -1.0;
73
74 // the event times are the floating leg's fixing times > 0 and zero itself, also mark those events where a notional
75 // decrease is admissable we might miss some payments, those are handled below
76 QL_REQUIRE(fixedNominal.size() > 0, "NumericLgmFlexiSwapEngine::calculate(): fixed nominal size is zero");
77 QL_REQUIRE(floatingNominal.size() > 0, "NumericLgmFlexiSwapEngine::calculate(): floating nominal size is zero");
78 Size legRatio = floatingNominal.size() / fixedNominal.size(); // we know there is no remainder
79 std::vector<Real> times;
80 std::vector<Date> dates;
81 std::vector<Size> fixCpnIndex, fltCpnIndex; // argument's vector index per event date / time
82 std::vector<Real> fixPayTime, fltPayTime; // per event date / time
83 Size firstAliveIndex = Null<Size>();
84 for (Size i = 0; i < floatingFixingDates.size(); ++i) {
85 Date d = floatingFixingDates[i];
86 if (d > today) {
87 if (firstAliveIndex == Null<Size>())
88 firstAliveIndex = i;
89 times.push_back(model()->parametrization()->termStructure()->timeFromReference(d));
90 dates.push_back(d);
91 fltCpnIndex.push_back(i);
92 fltPayTime.push_back(model()->parametrization()->termStructure()->timeFromReference(floatingPayDates[i]));
93 if (i % legRatio == 0) {
94 Size idx = static_cast<Size>(i / legRatio);
95 fixCpnIndex.push_back(idx);
96 fixPayTime.push_back(
97 model()->parametrization()->termStructure()->timeFromReference(fixedPayDates[idx]));
98 } else {
99 fixCpnIndex.push_back(Null<Size>());
100 fixPayTime.push_back(Null<Real>());
101 }
102 }
103 }
104
105 int n = times.size();
106
107 // construct swaption basket
108
109 std::vector<Real> swaptionVolTmp;
110 std::vector<Size> swaptionStartIdx, swaptionEndIdx;
111 // skip indices where there is no optionality (and the notional might also still increase)
112 // or where the lower notional bound is ignored, because the corresponding option date is in the past
113 Size i = 0;
114 while (i < fixedNominal.size() &&
115 (QuantLib::close_enough(fixedNominal[i], lowerNotionalBound[i]) || i * legRatio < firstAliveIndex))
116 ++i;
117 Size firstIndex = i;
118 for (; i < fixedNominal.size(); ++i) {
119 // volume attach and detach points for which we have to generate swaptions
120 Real currentVolUpper =
121 i == firstIndex ? fixedNominal[firstIndex] : std::min(lowerNotionalBound[i - 1], fixedNominal[i]);
122 Real currentVolLower = lowerNotionalBound[i];
123 if (!QuantLib::close_enough(currentVolUpper, currentVolLower)) {
124 for (Size j = i; j < fixedNominal.size(); ++j) {
125 Real nextNotional = j == fixedNominal.size() - 1 ? 0.0 : fixedNominal[j + 1];
126 if (nextNotional < currentVolUpper && !QuantLib::close_enough(currentVolLower, currentVolUpper)) {
127 Real tmpVol = std::min(currentVolUpper - nextNotional, currentVolUpper - currentVolLower);
128 if (!QuantLib::close_enough(tmpVol, 0.0)) {
129 swaptionStartIdx.push_back(i);
130 swaptionEndIdx.push_back(j + 1);
131 swaptionVolTmp.push_back(tmpVol);
132 currentVolUpper = std::max(nextNotional, currentVolLower);
133 }
134 }
135 }
136 QL_REQUIRE(QuantLib::close_enough(currentVolUpper, currentVolLower),
137 "NumericLgmFlexiSwapEngine:calculate(): currentVolUpper ("
138 << currentVolUpper << ") does not match currentVolLower (" << currentVolLower
139 << "), this is unexpected");
140 }
141 }
142
143 int m = swaptionVolTmp.size(); // number of generated swaptions
144
145 // compute equivalent number of swaptions with full grid, decide on whether to price a swaption array
146 // or a series of single swaptions
147
148 Real fullGridSwaptions = 0.0;
149 for (int i = 0; i < m; ++i) {
150 fullGridSwaptions += static_cast<Size>(swaptionEndIdx[i] - swaptionStartIdx[i]);
151 }
152 fullGridSwaptions /= static_cast<Real>(n);
153 Method effectiveMethod =
155 ? method_
157 // if we do not have any swaptions, swaption array is the only method that works (and which is fast also,
158 // since the operations on empty arrays do not cost much)
159 if (m == 0)
160 effectiveMethod = Method::SwaptionArray;
161
162 // per event date, per swaption, indicator if coupon belongs to underlying
163 std::vector<Array> underlyingMultiplier(n, Array(m, 0.0));
164 // per event date, per swaption, indicator if exercise is possible
165 std::vector<Array> exerciseIndicator(n, Array(m, 0.0));
166 // per swaption, its notional
167 Array notionals(m);
168
169 for (int i = 0; i < m; ++i) {
170 notionals[i] = swaptionVolTmp[i];
171 for (Size j = swaptionStartIdx[i]; j < swaptionEndIdx[i]; ++j) {
172 Size index = j * legRatio;
173 if (notionalCanBeDecreased[j] && index >= firstAliveIndex) {
174 index -= firstAliveIndex;
175 exerciseIndicator[index][i] = 1.0;
176 for (Size k = 0; k < legRatio; ++k) {
177 underlyingMultiplier[index + k][i] = swaptionVolTmp[i];
178 }
179 }
180 }
181 }
182
183 // model linked ibor index curve
184
185 iborModelCurve_ = QuantLib::ext::make_shared<LgmImpliedYtsFwdFwdCorrected>(model(), iborIndex->forwardingTermStructure());
186 iborModelIndex_ = iborIndex->clone(Handle<YieldTermStructure>(iborModelCurve_));
187
188 // x grid for each expiry
189
190 // underlying u and continuation value v for single swaption (_s) and array swaption (_a) approach)
191 std::vector<Array> u_a, v_a;
192 std::vector<Real> u_s, v_s;
193 if (effectiveMethod == Method::SingleSwaptions) {
194 u_s.resize(gridSize(), 0.0);
195 v_s.resize(gridSize(), 0.0);
196 } else {
197 u_a.resize(gridSize(), Array(m, 0.0));
198 v_a.resize(gridSize(), Array(m, 0.0));
199 }
200
201 Real undValAll0 = 0.0; // underlying value valued on the grid
202 int undValAllIdx = n + 1; // index until which we have collected the underlying coupons
203 Array value0(m, 0.0); // option values
204
205 // loop over swaption for single swaptions method, otherwise this is one loop
206 for (int sw = (effectiveMethod == Method::SingleSwaptions ? m - 1 : 0); sw >= 0; --sw) {
207
208 // per grid index underlying value (independent of swaptions, just to collect the underlying value)
209 std::vector<Real> uAll(gridSize(), 0.0);
210
211 // init at last grid point
212 // determine last time index for relevant swaption if in single swaption mode
213 // for the first swaption we start at the maximum always to make sure we collect
214 // all coupons for the underlying value
215 if (effectiveMethod == Method::SingleSwaptions && sw != m - 1) {
216 QL_REQUIRE(swaptionEndIdx[sw] * legRatio >= firstAliveIndex,
217 "swaptionEndIndex[" << sw << "] * legRatio (" << legRatio << ") < firstAliveIndex ("
218 << firstAliveIndex << ") - this is unexpected.");
219 n = swaptionEndIdx[sw] * legRatio - firstAliveIndex;
220 }
221
222 auto states = stateGrid(times[n - 1]);
223 for (Size k = 0; k < gridSize(); ++k) {
224 Real tmp = underlyingValue(states[k], times[n - 1], dates[n - 1], fltCpnIndex[n - 1], fixCpnIndex[n - 1],
225 fltPayTime[n - 1], fixPayTime[n - 1]);
226 // we can use the floating notional for both legs, since they have a consistent notional by construction
227 if (n < undValAllIdx) {
228 uAll[k] = tmp * floatingNominal[fltCpnIndex[n - 1]];
229 }
230 if (effectiveMethod == Method::SingleSwaptions) {
231 u_s[k] = tmp * underlyingMultiplier[n - 1][sw];
232 v_s[k] = exerciseIndicator[n - 1][sw] * std::max(-phi * u_s[k], 0.0);
233 } else {
234 u_a[k] = tmp * underlyingMultiplier[n - 1];
235 v_a[k] = exerciseIndicator[n - 1] * max(-phi * u_a[k], 0.0);
236 }
237 }
238
239 // roll back to first positive event time (in single swaption mode this might be > 1 though)
240 // for the last swaption we roll back to 1 in every case to make sure that we collect all
241 // coupons for the underlying value
242
243 int minIndex = 0;
244 if (effectiveMethod == Method::SingleSwaptions && sw != 0) {
245 QL_REQUIRE(swaptionStartIdx[sw] * legRatio >= firstAliveIndex,
246 "swaptionStartIndex[" << sw << "] * legRatio (" << legRatio << ") < firstAliveIndex ("
247 << firstAliveIndex << ") - this is unexpected.");
248 minIndex = swaptionStartIdx[sw] * legRatio - firstAliveIndex;
249 }
250
251 for (int j = n - 1; j > minIndex; j--) {
252 // rollback
253 auto states = stateGrid(times[j - 1]);
254 if (effectiveMethod == Method::SingleSwaptions) {
255 u_s = rollback(u_s, times[j], times[j - 1]);
256 v_s = rollback(v_s, times[j], times[j - 1]);
257 } else {
258 u_a = rollback(u_a, times[j], times[j - 1], Array(m, 0.0));
259 v_a = rollback(v_a, times[j], times[j - 1], Array(m, 0.0));
260 }
261 if (j < undValAllIdx) {
262 uAll = rollback(uAll, times[j], times[j - 1]);
263 }
264 // update
265 for (Size k = 0; k < gridSize(); ++k) {
266 Real tmp = underlyingValue(states[k], times[j - 1], dates[j - 1], fltCpnIndex[j - 1],
267 fixCpnIndex[j - 1], fltPayTime[j - 1], fixPayTime[j - 1]);
268 uAll[k] += (j < undValAllIdx ? tmp * floatingNominal[fltCpnIndex[j - 1]] : 0.0);
269 if (effectiveMethod == Method::SingleSwaptions) {
270 u_s[k] += tmp * underlyingMultiplier[j - 1][sw];
271 v_s[k] = exerciseIndicator[j - 1][sw] * std::max(v_s[k], -phi * u_s[k]) +
272 (1.0 - exerciseIndicator[j - 1][sw]) * v_s[k];
273 } else {
274 u_a[k] += tmp * underlyingMultiplier[j - 1];
275 v_a[k] = exerciseIndicator[j - 1] * max(v_a[k], -phi * u_a[k]) +
276 (1.0 - exerciseIndicator[j - 1]) * v_a[k];
277 }
278 } // for k (lgm grid)
279 } // for j (options)
280
281 // roll back to time zero
282
283 if (effectiveMethod == Method::SingleSwaptions) {
284 v_s = rollback(v_s, times[minIndex], 0.0);
285 } else {
286 v_a = rollback(v_a, times[minIndex], 0.0, Array(m, 0.0));
287 }
288 uAll = rollback(uAll, times[minIndex], 0.0);
289
290 // update undValAllIndx
291 undValAllIdx = minIndex + 1;
292
293 // populate option values
294 if (effectiveMethod == Method::SingleSwaptions)
295 value0[sw] = v_s[0];
296 else
297 value0 = v_a[0];
298
299 // update underlying value
300 undValAll0 += uAll[0];
301
302 } // for swaptions (in single swaption mode, otherwise this is a loop that runs once only)
303
304 // handle coupons we omitted above and add them to underlying value
305
306 Size minFltCpnIdx = fltCpnIndex.empty() ? 0 : *std::min_element(fltCpnIndex.begin(), fltCpnIndex.end());
307 Size minFixCpnIdx = fixCpnIndex.empty() ? 0 : *std::min_element(fixCpnIndex.begin(), fixCpnIndex.end());
308
309 for (Size i = 0; i < fixedCoupons.size(); ++i) {
310 if (fixedPayDates[i] > today && i < minFixCpnIdx) {
311 undValAll0 += (type == VanillaSwap::Payer ? -1.0 : 1.0) * fixedCoupons[i] *
312 (discountCurve_.empty() ? model()->parametrization()->termStructure() : discountCurve_)
313 ->discount(fixedPayDates[i]);
314 }
315 }
316
317 for (Size i = 0; i < floatingCoupons.size(); ++i) {
318 if (floatingPayDates[i] > today && i < minFltCpnIdx) {
319 QL_REQUIRE(floatingCoupons[i] != Null<Real>(),
320 "NumericLgmFlexiSwapEngineBase: no floating coupon provided for fixing date "
321 << floatingFixingDates[i]);
322 undValAll0 += (type == VanillaSwap::Payer ? 1.0 : -1.0) * floatingCoupons[i] *
323 (discountCurve_.empty() ? model()->parametrization()->termStructure() : discountCurve_)
324 ->discount(floatingPayDates[i]);
325 }
326 }
327
328 // sum over option values
329 Real sumOptions = std::accumulate(value0.begin(), value0.end(), 0.0);
330
331 // debug logging
332 // std::cerr << "Flexi-Swap pricing engine log:\n";
333 // std::cerr << "===========================\n";
334 // std::cerr << "underlying value = " << undValAll0 << "\n";
335 // std::cerr << "option value = " << phi * sumOptions << "\n";
336 // std::cerr << "===========================\n";
337 // std::cerr << "swaption basket (" << m << "):\n";
338 // Real sumNot = 0.0;
339 // for (Size i = 0; i < m; ++i) {
340 // std::cerr << "swaption #" << i << " (start,end)=(" << swaptionStartIdx[i] << "," << swaptionEndIdx[i]
341 // << ") notional = " << notionals[i] << " NPV = " << phi * value0[i] << "\n";
342 // sumNot += notionals[i];
343 // }
344 // std::cerr << "sum of swaption notional = " << sumNot << "\n";
345 // std::cerr << "number of equivalent full swaptions = " << fullGridSwaptions
346 // << ", singleSwaptionThreshold = " << singleSwaptionThreshold_ << "\n";
347 // std::cerr << "method = " << static_cast<int>(method_) << " (0=array, 1=single, 2=auto)\n";
348 // details
349 // std::cerr << "===========================\n";
350 // std::cerr << "times underlying exercise\n";
351 // for (Size i = 0; i < times.size(); ++i) {
352 // std::cerr << times[i] << " " << underlyingMultiplier[i] << " " << exerciseIndicator[i] << "\n";
353 // }
354 // end details
355 // std::cerr << "Flex-Swap pricing engine log end" << std::endl;
356 // end logging
357
358 return std::make_pair(phi * sumOptions + undValAll0, undValAll0);
359
360} // NumericLgmFlexiSwapEngineBase::calculate()
361
362NumericLgmFlexiSwapEngine::NumericLgmFlexiSwapEngine(const QuantLib::ext::shared_ptr<LinearGaussMarkovModel>& model,
363 const Real sy, const Size ny, const Real sx, const Size nx,
364 const Handle<YieldTermStructure>& discountCurve,
365 const Method method, const Real singleSwaptionThreshold)
366 : NumericLgmFlexiSwapEngineBase(model, sy, ny, sx, nx, discountCurve, method, singleSwaptionThreshold) {
367 registerWith(this->model());
368 registerWith(discountCurve_);
369} // NumericLgmFlexiSwapEngine::NumericLgmFlexiSwapEngine
370
372 // set arguments in base engine
373 type = arguments_.type;
374 fixedNominal = arguments_.fixedNominal;
375 floatingNominal = arguments_.floatingNominal;
376 fixedResetDates = arguments_.fixedResetDates;
377 fixedPayDates = arguments_.fixedPayDates;
378 floatingAccrualTimes = arguments_.floatingAccrualTimes;
379 floatingResetDates = arguments_.floatingResetDates;
380 floatingFixingDates = arguments_.floatingFixingDates;
381 floatingPayDates = arguments_.floatingPayDates;
382 fixedCoupons = arguments_.fixedCoupons;
383 fixedRate = arguments_.fixedRate;
384 floatingGearings = arguments_.floatingGearings;
385 floatingSpreads = arguments_.floatingSpreads;
386 cappedRate = arguments_.cappedRate;
387 flooredRate = arguments_.flooredRate;
388 floatingCoupons = arguments_.floatingCoupons;
389 iborIndex = arguments_.iborIndex;
390 lowerNotionalBound = arguments_.lowerNotionalBound;
391 optionPosition = arguments_.optionPosition;
392 notionalCanBeDecreased = arguments_.notionalCanBeDecreased;
393
394 // calculate and set results
396 results_.value = result.first;
397 results_.underlyingValue = result.second;
398 results_.additionalResults = getAdditionalResultsMap(model()->getCalibrationInfo());
399} // NumericLgmFlexiSwapEngine::calculate
400
401} // namespace QuantExt
const Instrument::results * results_
Definition: cdsoption.cpp:81
Numerical convolution solver for the LGM model.
std::vector< ValueType > rollback(const std::vector< ValueType > &v, const Real t1, const Real t0, const ValueType zero=ValueType(0.0)) const
const QuantLib::ext::shared_ptr< LinearGaussMarkovModel > & model() const
std::vector< Real > stateGrid(const Real t) const
Numerical engine for flexi swaps in the LGM model.
NumericLgmFlexiSwapEngineBase(const QuantLib::ext::shared_ptr< LinearGaussMarkovModel > &model, const Real sy, const Size ny, const Real sx, const Size nx, const Handle< YieldTermStructure > &discountCurve=Handle< YieldTermStructure >(), const Method method=Method::Automatic, const Real singleSwaptionThreshold=20.0)
const Handle< YieldTermStructure > discountCurve_
QuantLib::ext::shared_ptr< IborIndex > iborIndex
QuantLib::ext::shared_ptr< LgmImpliedYieldTermStructure > iborModelCurve_
QuantLib::ext::shared_ptr< IborIndex > iborModelIndex_
Real underlyingValue(const Real, const Real, const Date &, const Size, const Size, const Real, const Real) const
NumericLgmFlexiSwapEngine(const QuantLib::ext::shared_ptr< LinearGaussMarkovModel > &model, const Real sy, const Size ny, const Real sx, const Size nx, const Handle< YieldTermStructure > &discountCurve=Handle< YieldTermStructure >(), const Method method=Method::Automatic, const Real singleSwaptionThreshold=20.0)
std::map< std::string, boost::any > getAdditionalResultsMap(const LgmCalibrationInfo &info)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
numeric engine for flexi swaps in the LGM model
JY INF index sigma component.
Swap::arguments * arguments_