19#include <ql/math/matrixutilities/pseudosqrt.hpp>
20#include <ql/pricingengines/blackformula.hpp>
21#include <ql/processes/ornsteinuhlenbeckprocess.hpp>
26using std::adjacent_difference;
37namespace CommodityAveragePriceOptionMomementMatching {
45 const ext::shared_ptr<CommodityIndexedAverageCashFlow>& flow,
46 const ext::shared_ptr<QuantLib::BlackVolTermStructure>& vol,
47 const std::function<
double(
const QuantLib::Date& expiry1,
const QuantLib::Date& expiry2)>& rho,
48 QuantLib::Real strike) {
49 Date today = Settings::instance().evaluationDate();
55 std::vector<Date> futureExpiries;
56 std::map<Date, Real> futureVols;
57 std::vector<double> spotVariances;
58 size_t n = flow->indices().size();
59 double atmUnderlyingCcy = 0;
61 for (
const auto& [pricingDate, index] : flow->indices()) {
62 Date fixingDate = index->fixingCalendar().adjust(pricingDate, Preceding);
63 Real fxRate = (flow->fxIndex()) ? flow->fxIndex()->fixing(fixingDate) : 1.0;
67 res.
fixings.push_back(index->fixing(fixingDate) * fxRate);
68 if (pricingDate <= today) {
71 atmUnderlyingCcy = index->fixing(fixingDate);
73 res.
times.push_back(vol->timeFromReference(pricingDate));
75 double K = strike == Null<Real>() ? atmUnderlyingCcy : strike;
76 if (flow->useFuturePrice()) {
77 Date expiry = index->expiryDate();
78 futureExpiries.push_back(expiry);
79 if (futureVols.count(expiry) == 0) {
80 futureVols[expiry] = vol->blackVol(expiry, K);
83 spotVariances.push_back(
84 vol->blackVariance(res.
times.back(), K));
85 res.
spotVols.push_back(std::sqrt(spotVariances.back() / res.
times.back()));
90 res.
accruals /=
static_cast<double>(n);
91 EA /=
static_cast<double>(n);
97 if (flow->useFuturePrice()) {
99 for (Size i = 0; i < res.
forwards.size(); ++i) {
100 Date e_i = futureExpiries[i];
101 Volatility v_i = futureVols.at(e_i);
104 for (Size j = 0; j < i; ++j) {
105 Date e_j = futureExpiries[j];
106 Volatility v_j = futureVols.at(e_j);
112 for (Size i = 0; i < res.
forwards.size(); ++i) {
114 for (Size j = 0; j < i; ++j) {
119 EA2 /= std::pow(
static_cast<double>(n), 2.0);
122 QL_REQUIRE(!std::isinf(EA2),
123 "moment matching fails (EA2 = inf) - this is possibly due to too high input volatilities.");
126 if (!res.
times.empty()) {
128 double s = EA2 / (EA * EA);
131 if (s < 1.0 || QuantLib::close_enough(s, 1)) {
134 res.
sigma = std::sqrt(std::log(s) / res.
tn);
145 const Handle<YieldTermStructure>& discountCurve,
const QuantLib::Handle<QuantExt::BlackScholesModelWrapper>& model,
147 : discountCurve_(discountCurve), volStructure_(model->processes().front()->blackVolatility()), beta_(beta) {
148 QL_REQUIRE(
beta_ >= 0.0,
"beta >= 0 required, found " <<
beta_);
153 const QuantLib::Handle<QuantLib::YieldTermStructure>& discountCurve,
154 const QuantLib::Handle<QuantLib::BlackVolTermStructure>& vol, QuantLib::Real beta)
155 : discountCurve_(discountCurve), volStructure_(vol), beta_(beta) {
156 QL_REQUIRE(
beta_ >= 0.0,
"beta >= 0 required, found " <<
beta_);
162 if (
beta_ == 0.0 || ed_1 == ed_2) {
167 return exp(-
beta_ * fabs(t_2 - t_1));
177 Date today = Settings::instance().evaluationDate();
181 if (today >=
arguments_.flow->indices().rbegin()->first) {
184 Real omega =
arguments_.type == Option::Call ? 1.0 : -1.0;
196 if (effectiveStrike <= 0.0) {
211 Real lastFixing = 0.0;
213 for (
const auto& kv :
arguments_.flow->indices()) {
215 if (today < kv.first) {
222 lastFixing = fxRate*kv.second->fixing(kv.first);
223 if (
arguments_.barrierStyle == Exercise::American)
227 if (
arguments_.barrierStyle == Exercise::European)
252 return ((
arguments_.barrierType == Barrier::DownIn ||
arguments_.barrierType == Barrier::UpIn) &&
260 QL_REQUIRE(
arguments_.barrierLevel == Null<Real>(),
261 "CommodityAveragePriceOptionAnalyticalEngine does not support barrier feature. Use MC engine instead.");
264 auto& mp =
results_.additionalResults;
271 mp[
"discount"] = discount;
277 mp[
"effective_strike"] =
arguments_.effectiveStrike;
285 QL_REQUIRE(effectiveStrike > 0.0,
"calculateSpot: expected effectiveStrike to be positive");
292 ::placeholders::_2), effectiveStrike);
295 mp[
"futureVols"] = matchedMoments.futureVols;
297 mp[
"spotVols"] = matchedMoments.spotVols;
302 blackFormula(
arguments_.type, effectiveStrike, matchedMoments.firstMoment(),
303 matchedMoments.stdDev(), discount);
307 mp[
"effective_strike"] = effectiveStrike;
308 mp[
"forward"] = matchedMoments.forward;
309 mp[
"exp_A_2"] = matchedMoments.EA2;
310 mp[
"tte"] = matchedMoments.timeToExpriy();
311 mp[
"sigma"] = matchedMoments.sigma;
313 mp[
"times"] = matchedMoments.times;
314 mp[
"forwards"] = matchedMoments.forwards;
335 Real omega =
arguments_.type == Option::Call ? 1.0 : -1.0;
346 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(dt.size(),
seed_);
351 QL_REQUIRE(effectiveStrike > 0.0,
"calculateSpot: expected effectiveStrike to be positive");
359 Array expHalfFwdVar(dt.size(), 0.0);
360 Array fwdStdDev(dt.size(), 0.0);
361 Array fwdRatio(dt.size(), 0.0);
363 for (Size i = 0; i < dt.size(); i++) {
365 expHalfFwdVar[i] =
volStructure_->blackForwardVariance(t - dt[i], t, effectiveStrike);
366 fwdStdDev[i] =
sqrt(expHalfFwdVar[i]);
367 expHalfFwdVar[i] =
exp(-expHalfFwdVar[i] / 2.0);
370 fxRate =
arguments_.flow->fxIndex()->fixing(dates[i+1]);
371 fwdRatio[i] = fxRate *
arguments_.flow->index()->fixing(dates[i + 1]);
374 fxRate =
arguments_.flow->fxIndex()->fixing(dates[i]);
375 fwdRatio[i] /= (fxRate *
arguments_.flow->index()->fixing(dates[i]));
378 Array factors = expHalfFwdVar * fwdRatio;
382 for (Size k = 0; k <
samples_; k++) {
385 vector<Real> sequence = rsg.nextSequence().value;
389 Real samplePayoff = 0.0;
390 Array path(sequence.begin(), sequence.end());
391 path = Exp(path * fwdStdDev) * factors;
393 for (Size i = 0; i < dt.size(); i++) {
396 price = i == 0 ? path[i] : price * path[i];
399 samplePayoff += price;
402 if (
arguments_.barrierStyle == Exercise::American)
409 samplePayoff =
max(omega * (samplePayoff - effectiveStrike), 0.0);
412 if (
arguments_.barrierStyle == Exercise::European)
420 payoff = k == 0 ? samplePayoff : payoff * k / (k + 1) + samplePayoff / (k + 1);
437 Real omega =
arguments_.type == Option::Call ? 1.0 : -1.0;
445 QL_REQUIRE(effectiveStrike > 0.0,
"calculateFuture: expected effectiveStrike to be positive");
451 vector<Size> futureIndex;
452 setupFuture(vols, sqrtCorr, prices, futureIndex, effectiveStrike);
465 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(vols.size() * dt.size(),
seed_);
468 Matrix drifts(vols.size(), dt.size(), 0.0);
469 Matrix stdDev(vols.size(), dt.size(), 0.0);
470 Array logPrices(vols.size());
471 for (Size i = 0; i < drifts.rows(); i++) {
472 logPrices[i] = std::log(prices[i]);
473 for (Size j = 0; j < drifts.columns(); j++) {
474 drifts[i][j] = -vols[i] * vols[i] * dt[j] / 2.0;
475 stdDev[i][j] = vols[i] *
sqrt(dt[j]);
481 Matrix paths(vols.size(), dt.size());
482 for (Size k = 0; k <
samples_; ++k) {
485 const vector<Real>& sequence = rsg.nextSequence().value;
488 std::copy(sequence.begin(), sequence.end(), paths.begin());
491 paths = sqrtCorr * paths;
494 for (Size i = 0; i < paths.rows(); ++i) {
495 for (Size j = 0; j < paths.columns(); ++j) {
496 paths[i][j] = (j == 0 ? logPrices[i] : paths[i][j - 1]) + drifts[i][j] + stdDev[i][j] * paths[i][j];
501 Real samplePayoff = 0.0;
504 for (Size j = 0; j < dt.size(); ++j) {
505 price = paths[futureIndex[j]][j];
506 if (
arguments_.barrierStyle == Exercise::American)
508 samplePayoff += std::exp(price);
515 samplePayoff =
max(omega * (samplePayoff - effectiveStrike), 0.0);
518 if (
arguments_.barrierStyle == Exercise::European)
526 payoff = k == 0 ? samplePayoff : payoff * k / (k + 1) + samplePayoff / (k + 1);
534 vector<Real>& prices, vector<Size>& futureIndex,
538 outVolatilities.clear();
543 map<Date, Real> result;
552 Date today = Settings::instance().evaluationDate();
553 set<Date> expiryDates;
554 for (
const auto& p :
arguments_.flow->indices()) {
555 if (p.first > today) {
556 Date expiry = p.second->expiryDate();
558 if (expiryDates.insert(expiry).second) {
559 outVolatilities.push_back(
volStructure_->blackVol(expiry, strike));
562 fxRate =
arguments_.flow->fxIndex()->fixing(expiry);
563 prices.push_back(fxRate*p.second->fixing(today));
565 futureIndex.push_back(expiryDates.size() - 1);
570 vector<Date> vExpiryDates(expiryDates.begin(), expiryDates.end());
571 outSqrtCorr = Matrix(vExpiryDates.size(), vExpiryDates.size(), 1.0);
572 for (Size i = 0; i < vExpiryDates.size(); i++) {
573 for (Size j = 0; j < i; j++) {
574 outSqrtCorr[i][j] = outSqrtCorr[j][i] =
rho(vExpiryDates[i], vExpiryDates[j]);
577 outSqrtCorr = pseudoSqrt(outSqrtCorr, SalvagingAlgorithm::None);
586 Date today = Settings::instance().evaluationDate();
587 outDates.push_back(today);
588 for (
const auto& p :
arguments_.flow->indices()) {
589 if (p.first > today) {
590 outDates.push_back(p.first);
596 vector<Real> dt(times.size());
597 adjacent_difference(times.begin(), times.end(), dt.begin());
const Instrument::results * results_
void calculate() const override
bool barrierTriggered(const Real price, const bool logPrice) const
QuantLib::Real rho(const QuantLib::Date &ed_1, const QuantLib::Date &ed_2) const
Return the correlation between two future expiry dates ed_1 and ed_2.
CommodityAveragePriceOptionBaseEngine(const QuantLib::Handle< QuantLib::YieldTermStructure > &discountCurve, const QuantLib::Handle< QuantExt::BlackScholesModelWrapper > &model, QuantLib::Real beta=0.0)
bool isModelDependent() const
QuantLib::Handle< QuantLib::YieldTermStructure > discountCurve_
QuantLib::Real logBarrier_
QuantLib::Handle< QuantLib::BlackVolTermStructure > volStructure_
bool alive(const bool barrierTriggered) const
void calculate() const override
void calculateFuture() const
Calculations when underlying swap references a commodity spot price.
void setupFuture(std::vector< QuantLib::Real > &outVolatilities, QuantLib::Matrix &outSqrtCorr, std::vector< QuantLib::Real > &outPrices, std::vector< QuantLib::Size > &futureIndex, QuantLib::Real strike) const
std::vector< QuantLib::Real > timegrid(std::vector< QuantLib::Date > &outDates) const
void calculateSpot() const
Calculations when underlying swap references a commodity spot price.
commodity average price option engine
Cash flow dependent on a single commodity spot price or future's settlement price.
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)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
std::vector< QuantLib::Date > indexExpiries
std::vector< QuantLib::Date > pricingDates
std::vector< Real > forwards
std::vector< QuantLib::Real > times
std::vector< Real > futureVols
std::vector< std::string > indexNames
std::vector< QuantLib::Real > fixings
std::vector< Real > spotVols
Swap::arguments * arguments_