1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
Copyright (C) 2008 Roland Lichters
This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/
QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license. You should have received a
copy of the license along with this program; if not, please email
<quantlib-dev@lists.sf.net>. The license is also available online at
<https://www.quantlib.org/license.shtml>.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the license for more details.
*/
#include <ql/experimental/credit/syntheticcdo.hpp>
#ifndef QL_PATCH_SOLARIS
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/event.hpp>
#include <ql/math/solvers1d/brent.hpp>
#include <ql/termstructures/yieldtermstructure.hpp>
#include <ql/experimental/credit/gaussianlhplossmodel.hpp>
#include <ql/experimental/credit/midpointcdoengine.hpp>
#include <ql/optional.hpp>
using namespace std;
namespace QuantLib {
SyntheticCDO::SyntheticCDO(const ext::shared_ptr<Basket>& basket,
Protection::Side side,
Schedule schedule,
Rate upfrontRate,
Rate runningRate,
const DayCounter& dayCounter,
BusinessDayConvention paymentConvention,
ext::optional<Real> notional)
: basket_(basket), side_(side), upfrontRate_(upfrontRate), runningRate_(runningRate),
leverageFactor_(notional ? *notional / basket->trancheNotional() : Real(1.)), // NOLINT(readability-implicit-bool-conversion)
dayCounter_(dayCounter), paymentConvention_(paymentConvention) {
QL_REQUIRE(!basket->names().empty(), "basket is empty");
// Basket inception must lie before contract protection start.
QL_REQUIRE(basket->refDate() <= schedule.startDate(),
//using the start date of the schedule might be wrong, think of the
// CDS rule
"Basket did not exist before contract start.");
// Notice the notional is that of the basket at basket inception, some
// names might have defaulted in between
normalizedLeg_ = FixedRateLeg(std::move(schedule))
.withNotionals(basket_->trancheNotional() * leverageFactor_)
.withCouponRates(runningRate, dayCounter)
.withPaymentAdjustment(paymentConvention);
// Date today = Settings::instance().evaluationDate();
// register with probabilities if the corresponding issuer is, baskets
// are not registered with the DTS
for (Size i = 0; i < basket->names().size(); i++) {
/* This turns out to be a problem: depends on today but I am not
modifying the registrations, if we go back in time in the
calculations this would left me unregistered to some. Not impossible
to de-register and register when updating but i am dropping it.
if(!basket->pool()->get(basket->names()[i]).
defaultedBetween(schedule.dates()[0], today,
basket->pool()->defaultKeys()[i]))
*/
// registers with the associated curve (issuer and event type)
// \todo make it possible to access them by name instead of index
registerWith(basket->pool()->get(basket->names()[i]).
defaultProbability(basket->pool()->defaultKeys()[i]));
/* \todo Issuers should be observables/obsrvr and they would in turn
regiter with the DTS; only we might get updates from curves we do
not use.
*/
}
registerWith(basket_);
}
Rate SyntheticCDO::premiumValue () const {
calculate();
return premiumValue_;
}
Rate SyntheticCDO::protectionValue () const {
calculate();
return protectionValue_;
}
Real SyntheticCDO::premiumLegNPV() const {
calculate();
if(side_ == Protection::Buyer) return premiumValue_;
return -premiumValue_;
}
Real SyntheticCDO::protectionLegNPV() const {
calculate();
if(side_ == Protection::Buyer) return -protectionValue_;
return protectionValue_;
}
Rate SyntheticCDO::fairPremium () const {
calculate();
QL_REQUIRE(premiumValue_ != 0, "Attempted divide by zero while calculating syntheticCDO premium.");
return runningRate_
* (protectionValue_ - upfrontPremiumValue_) / premiumValue_;
}
Rate SyntheticCDO::fairUpfrontPremium () const {
calculate();
return (protectionValue_ - premiumValue_) / remainingNotional_;
}
std::vector<Real> SyntheticCDO::expectedTrancheLoss() const {
calculate();
return expectedTrancheLoss_;
}
Size SyntheticCDO::error () const {
calculate();
return error_;
}
bool SyntheticCDO::isExpired () const {
// FIXME: it could have also expired (knocked out) because theres
// no remaining tranche notional.
return detail::simple_event(normalizedLeg_.back()->date())
.hasOccurred();
}
Real SyntheticCDO::remainingNotional() const {
calculate();
return remainingNotional_;
}
void SyntheticCDO::setupArguments(PricingEngine::arguments* args) const {
auto* arguments = dynamic_cast<SyntheticCDO::arguments*>(args);
QL_REQUIRE(arguments != nullptr, "wrong argument type");
arguments->basket = basket_;
arguments->side = side_;
arguments->normalizedLeg = normalizedLeg_;
arguments->upfrontRate = upfrontRate_;
arguments->runningRate = runningRate_;
arguments->dayCounter = dayCounter_;
arguments->paymentConvention = paymentConvention_;
arguments->leverageFactor = leverageFactor_;
}
void SyntheticCDO::fetchResults(const PricingEngine::results* r) const {
Instrument::fetchResults(r);
const auto* results = dynamic_cast<const SyntheticCDO::results*>(r);
QL_REQUIRE(results != nullptr, "wrong result type");
premiumValue_ = results->premiumValue;
protectionValue_ = results->protectionValue;
upfrontPremiumValue_ = results->upfrontPremiumValue;
remainingNotional_ = results->remainingNotional;
error_ = results->error;
expectedTrancheLoss_ = results->expectedTrancheLoss;
}
void SyntheticCDO::setupExpired() const {
Instrument::setupExpired();
premiumValue_ = 0.0;
protectionValue_ = 0.0;
upfrontPremiumValue_ = 0.0;
remainingNotional_ = 1.0;
expectedTrancheLoss_.clear();
}
void SyntheticCDO::arguments::validate() const {
QL_REQUIRE(side != Protection::Side(-1), "side not set");
QL_REQUIRE(basket && !basket->names().empty(), "no basket given");
QL_REQUIRE(runningRate != Null<Real>(), "no premium rate given");
QL_REQUIRE(upfrontRate != Null<Real>(), "no upfront rate given");
QL_REQUIRE(!dayCounter.empty(), "no day counter given");
}
void SyntheticCDO::results::reset() {
Instrument::results::reset();
premiumValue = Null<Real>();
protectionValue = Null<Real>();
upfrontPremiumValue = Null<Real>();
remainingNotional = Null<Real>();
error = 0;
expectedTrancheLoss.clear();
}
namespace {
class ObjectiveFunction {
public:
ObjectiveFunction(Real target,
SimpleQuote& quote,
PricingEngine& engine,
const SyntheticCDO::results* results)
: target_(target), quote_(quote),
engine_(engine), results_(results) {}
Real operator()(Real guess) const {
quote_.setValue(guess);
engine_.calculate();
return results_->value - target_;
}
private:
Real target_;
SimpleQuote& quote_;
PricingEngine& engine_;
const SyntheticCDO::results* results_;
};
}
// untested, not sure this is not messing up, once it comes out of this
// the basket model is different.....
Real SyntheticCDO::implicitCorrelation(const std::vector<Real>& recoveries,
const Handle<YieldTermStructure>& discountCurve,
Real targetNPV,
Real accuracy) const
{
ext::shared_ptr<SimpleQuote> correl(new SimpleQuote(0.0));
ext::shared_ptr<GaussianLHPLossModel> lhp(new
GaussianLHPLossModel(Handle<Quote>(correl), recoveries));
// lock
basket_->setLossModel(lhp);
MidPointCDOEngine engineIC(discountCurve);
setupArguments(engineIC.getArguments());
const auto* results = dynamic_cast<const SyntheticCDO::results*>(engineIC.getResults());
// aviod recal of the basket on engine updates through the quote
basket_->recalculate();
basket_->freeze();
ObjectiveFunction f(targetNPV, *correl, engineIC, results);
Rate guess = 0.001;
// Rate step = guess*0.1;
// wrap/catch to be able to unfreeze the basket:
Real solution = Brent().solve(f, accuracy, guess, QL_EPSILON, 1.-QL_EPSILON);
basket_->unfreeze();
return solution;
}
}
#endif
|