Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commodityspreadoptionengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2022 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
19#include <iostream>
20#include <ql/math/matrixutilities/pseudosqrt.hpp>
21#include <ql/pricingengines/blackformula.hpp>
22#include <ql/processes/ornsteinuhlenbeckprocess.hpp>
28
29using std::adjacent_difference;
30using std::exp;
31using std::make_pair;
32using std::map;
33using std::max;
34using std::pair;
35using std::set;
36using std::vector;
37
38namespace QuantExt {
39
41 const Handle<YieldTermStructure>& discountCurve, const QuantLib::Handle<QuantLib::BlackVolTermStructure>& volLong,
42 const QuantLib::Handle<QuantLib::BlackVolTermStructure>& volShort,
43 const QuantLib::Handle<QuantExt::CorrelationTermStructure>& rho, Real beta)
44 : discountCurve_(discountCurve), volTSLongAsset_(volLong), volTSShortAsset_(volShort), rho_(rho), beta_(beta) {
45 QL_REQUIRE(beta_ >= 0.0, "beta >= 0 required, found " << beta_);
46 registerWith(discountCurve_);
47 registerWith(volTSLongAsset_);
48 registerWith(volTSShortAsset_);
49}
50
52 // Populate some additional results that don't change
53 auto& mp = results_.additionalResults;
54 QL_REQUIRE(arguments_.exercise->type() == QuantLib::Exercise::European, "Only European Spread Option supported");
55 QL_REQUIRE(arguments_.longAssetFlow && arguments_.shortAssetFlow, "flows can not be null");
56
57 Date today = Settings::instance().evaluationDate();
58
59 Date exerciseDate = arguments_.exercise->lastDate();
60
61 Date paymentDate = arguments_.paymentDate;
62 if (paymentDate == Date())
63 paymentDate = std::max(arguments_.longAssetFlow->date(), arguments_.shortAssetFlow->date());
64
65 QL_REQUIRE(paymentDate >= exerciseDate, "Payment date needs to be on or after exercise date");
66
67 double df = discountCurve_->discount(paymentDate);
68
69 Time ttp = discountCurve_->timeFromReference(paymentDate);
70 Time tte = discountCurve_->timeFromReference(exerciseDate);
71
72 auto parameterFlow1 =
74
75 auto parameterFlow2 =
76 derivePricingParameterFromFlow(arguments_.shortAssetFlow, *volTSShortAsset_, arguments_.shortAssetFxIndex);
77
78 double F1 = parameterFlow1.atm;
79 double F2 = parameterFlow2.atm;
80 double sigma1 = parameterFlow1.sigma;
81 double sigma2 = parameterFlow2.sigma;
82 double obsTime1 = parameterFlow1.tn;
83 double obsTime2 = parameterFlow2.tn;
84 double accruals1 = parameterFlow1.accruals;
85 double accruals2 = parameterFlow2.accruals;
86
87 double sigma = 0;
88 double stdDev = 0;
89 double Y = 0;
90 double Z = 0;
91 double sigmaY = 0;
92 double w1 = arguments_.longAssetFlow->gearing();
93 double w2 = arguments_.shortAssetFlow->gearing();
94 // Adjust strike for past fixings
95 double effectiveStrike = arguments_.effectiveStrike - w1 * accruals1 + w2 * accruals2;
96 Real correlation = QuantLib::Null<Real>();
97
98 if (exerciseDate <= today && paymentDate <= today) {
99 results_.value = 0;
100 } else if (exerciseDate <= today && paymentDate > today) {
101 // if observation time is before expiry, continue the process with zero vol and zero drift from pricing date to
102 // expiry
103 double omega = arguments_.type == Option::Call ? 1 : -1;
104
105 results_.value = df * arguments_.quantity * omega * std::max(w1 * F1 - w2 * F2 - effectiveStrike, 0.0);
106
107 } else if (effectiveStrike + F2 * w2 < 0) {
108 // Effective strike can be become negative if accrueds large enough
109 if (arguments_.type == Option::Call) {
110 results_.value = df * arguments_.quantity * std::max(w1 * F1 - w2 * F2 - effectiveStrike, 0.0);
111 } else {
112 results_.value = 0.0;
113 }
114
115 } else {
116 sigma1 = sigma1 * std::min(1.0, std::sqrt(obsTime1 / tte));
117 sigma2 = sigma2 * std::min(1.0, std::sqrt(obsTime2 / tte));
118 correlation = rho();
119 // KirkFormula
120 Y = (F2 * w2 + effectiveStrike);
121 Z = w1 * F1 / Y;
122 sigmaY = sigma2 * F2 * w2 / Y;
123
124 sigma = std::sqrt(std::pow(sigma1, 2.0) + std::pow(sigmaY, 2.0) - 2 * sigma1 * sigmaY * correlation);
125
126 stdDev = sigma * sqrt(tte);
127
128 results_.value = arguments_.quantity * Y * blackFormula(arguments_.type, 1, Z, stdDev, df);
129 }
130
131 // Calendar spread adjustment if observation period is before the exercise date
132 mp["F1"] = F1;
133 mp["accruals1"] = accruals1;
134 mp["sigma1"] = sigma1;
135 mp["obsTime1"] = obsTime1;
136 mp["F2"] = F2;
137 mp["accruals2"] = accruals2;
138 mp["sigma2"] = sigma2;
139 mp["obsTime2"] = obsTime2;
140 mp["tte"] = tte;
141 mp["ttp"] = ttp;
142 mp["df"] = df;
143 mp["sigma"] = sigma;
144 mp["stdDev"] = stdDev;
145 mp["Y"] = Y;
146 mp["Z"] = Z;
147 mp["sigma_Y"] = sigmaY;
148 mp["quantity"] = arguments_.quantity;
149 mp["npv"] = results_.value;
150 mp["exerciseDate"] = exerciseDate;
151 mp["paymentDate"] = paymentDate;
152 mp["w1"] = w1;
153 mp["w2"] = w2;
154 mp["rho"] = correlation;
155 mp["index1_pricingDates"] = parameterFlow1.pricingDates;
156 mp["index1_index"] = parameterFlow1.indexNames;
157 mp["index1_index_expiry"] = parameterFlow1.expiries;
158 mp["index1_fixing"] = parameterFlow1.fixings;
159 mp["index2_pricingDates"] = parameterFlow2.pricingDates;
160 mp["index2_index"] = parameterFlow2.indexNames;
161 mp["index2_index_expiry"] = parameterFlow2.expiries;
162 mp["index2_fixing"] = parameterFlow2.fixings;
163}
164
167 const ext::shared_ptr<BlackVolTermStructure>& vol,
168 const ext::shared_ptr<FxIndex>& fxIndex) const {
170 if (auto cf = ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(flow)) {
171 res.accruals = 0.0;
172 res.tn = vol->timeFromReference(cf->pricingDate());
173 double fxSpot = 1.0;
174 if (fxIndex) {
175 fxSpot = fxIndex->fixing(cf->pricingDate());
176 }
177 double atmUnderlyingCurrency = cf->index()->fixing(cf->pricingDate());
178 res.atm = atmUnderlyingCurrency * fxSpot;
179 res.sigma = res.tn > 0 && !QuantLib::close_enough(res.tn, 0.0)
180 ? vol->blackVol(res.tn, atmUnderlyingCurrency, true)
181 : 0.0;
182 res.indexNames.push_back(cf->index()->name());
183 res.expiries.push_back(cf->index()->expiryDate());
184 res.fixings.push_back(atmUnderlyingCurrency);
185 res.pricingDates.push_back(cf->pricingDate());
186 } else if (auto avgCf = ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(flow)) {
188 avgCf, vol,
189 std::bind(&CommoditySpreadOptionAnalyticalEngine::intraAssetCorrelation, this, std::placeholders::_1,
190 std::placeholders::_2, vol));
191 res.tn = parameter.tn;
192 res.atm = parameter.forward;
193 res.accruals = parameter.accruals;
194 res.sigma = parameter.sigma;
195 res.indexNames = parameter.indexNames;
196 res.expiries = parameter.indexExpiries;
197 res.fixings = parameter.fixings;
198 res.pricingDates = parameter.pricingDates;
199 } else {
200 QL_FAIL("SpreadOptionEngine supports only CommodityIndexedCashFlow or CommodityIndexedAverageCashFlow");
201 }
202 return res;
203}
204
206 const Date& ed_1, const Date& ed_2, const ext::shared_ptr<BlackVolTermStructure>& vol) const {
207 if (beta_ == 0.0 || ed_1 == ed_2) {
208 return 1.0;
209 } else {
210 Time t_1 = vol->timeFromReference(ed_1);
211 Time t_2 = vol->timeFromReference(ed_2);
212 return exp(-beta_ * fabs(t_2 - t_1));
213 }
214}
215
217 if (arguments_.longAssetFlow->index()->underlyingName() != arguments_.shortAssetFlow->index()->underlyingName()) {
218 return rho_->correlation(arguments_.exercise->lastDate());
219 } else {
220 return intraAssetCorrelation(arguments_.shortAssetLastPricingDate, arguments_.longAssetLastPricingDate,
222 }
223}
224
225} // namespace QuantExt
const Instrument::results * results_
Definition: cdsoption.cpp:81
const QuantLib::Handle< QuantExt::CorrelationTermStructure > rho_
QuantLib::Handle< QuantLib::BlackVolTermStructure > volTSShortAsset_
QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve_
QuantLib::Handle< QuantLib::BlackVolTermStructure > volTSLongAsset_
CommoditySpreadOptionAnalyticalEngine(const QuantLib::Handle< QuantLib::YieldTermStructure > &discountCurve, const QuantLib::Handle< QuantLib::BlackVolTermStructure > &volTSLongAsset, const QuantLib::Handle< QuantLib::BlackVolTermStructure > &volTSShortAsset, const QuantLib::Handle< QuantExt::CorrelationTermStructure > &rho, Real beta=0.0)
double intraAssetCorrelation(const QuantLib::Date &e1, const QuantLib::Date &e2, const ext::shared_ptr< BlackVolTermStructure > &vol) const
Return the correlation between two future expiry dates ed_1 and ed_2.
PricingParameter derivePricingParameterFromFlow(const ext::shared_ptr< CommodityCashFlow > &flow, const ext::shared_ptr< BlackVolTermStructure > &vol, const ext::shared_ptr< FxIndex > &fxIndex) const
commodity average price option engine
Cash flow dependent on the average commodity spot price or future's settlement price over a period....
Cash flow dependent on a single commodity spot price or future's settlement price.
commodity spread option engine
base class for multi path generators
MomentMatchingResults matchFirstTwoMomentsTurnbullWakeman(const ext::shared_ptr< CommodityIndexedAverageCashFlow > &flow, const ext::shared_ptr< QuantLib::BlackVolTermStructure > &vol, const std::function< double(const QuantLib::Date &expiry1, const QuantLib::Date &expiry2)> &rho, QuantLib::Real strike)
RandomVariable sqrt(RandomVariable x)
CompiledFormula exp(CompiledFormula x)
Swap::arguments * arguments_