Skip to content

Commit

Permalink
Merge pull request #1356.
Browse files Browse the repository at this point in the history
Turnbull-Wakeman engine for discrete Asian options
  • Loading branch information
lballabio authored May 7, 2022
2 parents 2b6904e + 39f16f7 commit 30121a0
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 2 deletions.
2 changes: 2 additions & 0 deletions QuantLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@
<ClInclude Include="ql\experimental\asian\all.hpp" />
<ClInclude Include="ql\experimental\asian\analytic_cont_geom_av_price_heston.hpp" />
<ClInclude Include="ql\experimental\asian\analytic_discr_geom_av_price_heston.hpp" />
<ClInclude Include="ql\pricingengines\asian\turnbullwakemanasianengine.hpp" />
<ClInclude Include="ql\experimental\averageois\all.hpp" />
<ClInclude Include="ql\experimental\averageois\arithmeticaverageois.hpp" />
<ClInclude Include="ql\experimental\averageois\arithmeticoisratehelper.hpp" />
Expand Down Expand Up @@ -1922,6 +1923,7 @@
<ClCompile Include="ql\experimental\amortizingbonds\amortizingfloatingratebond.cpp" />
<ClCompile Include="ql\experimental\asian\analytic_cont_geom_av_price_heston.cpp" />
<ClCompile Include="ql\experimental\asian\analytic_discr_geom_av_price_heston.cpp" />
<ClCompile Include="ql\pricingengines\asian\turnbullwakemanasianengine.cpp" />
<ClCompile Include="ql\experimental\averageois\arithmeticaverageois.cpp" />
<ClCompile Include="ql\experimental\averageois\arithmeticoisratehelper.cpp" />
<ClCompile Include="ql\experimental\averageois\averageoiscouponpricer.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions QuantLib.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -4389,6 +4389,9 @@
<Filter>instruments</Filter>
</ClInclude>
<ClInclude Include="ql\pricingengines\bond\riskybondengine.hpp" />
<ClInclude Include="ql\pricingengines\asian\turnbullwakemanasianengine.hpp">
<Filter>pricingengines\asian</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ql\methods\montecarlo\brownianbridge.cpp">
Expand Down Expand Up @@ -7101,5 +7104,8 @@
<Filter>instruments</Filter>
</ClCompile>
<ClCompile Include="ql\pricingengines\bond\riskybondengine.cpp" />
<ClCompile Include="ql\pricingengines\asian\turnbullwakemanasianengine.cpp">
<Filter>pricingengines\asian</Filter>
</ClCompile>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions ql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ set(QL_SOURCES
pricingengines/asian/mc_discr_arith_av_strike.cpp
pricingengines/asian/mc_discr_geom_av_price.cpp
pricingengines/asian/mc_discr_geom_av_price_heston.cpp
pricingengines/asian/turnbullwakemanasianengine.cpp
pricingengines/barrier/analyticbarrierengine.cpp
pricingengines/barrier/analyticbinarybarrierengine.cpp
pricingengines/barrier/discretizedbarrieroption.cpp
Expand Down Expand Up @@ -1840,6 +1841,7 @@ set(QL_HEADERS
pricingengines/asian/mc_discr_geom_av_price.hpp
pricingengines/asian/mc_discr_geom_av_price_heston.hpp
pricingengines/asian/mcdiscreteasianenginebase.hpp
pricingengines/asian/turnbullwakemanasianengine.hpp
pricingengines/barrier/analyticbarrierengine.hpp
pricingengines/barrier/analyticbinarybarrierengine.hpp
pricingengines/barrier/binomialbarrierengine.hpp
Expand Down
6 changes: 4 additions & 2 deletions ql/pricingengines/asian/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ this_include_HEADERS = \
mc_discr_arith_av_strike.hpp \
mc_discr_geom_av_price.hpp \
mc_discr_geom_av_price_heston.hpp \
mcdiscreteasianenginebase.hpp
mcdiscreteasianenginebase.hpp \
turnbullwakemanasianengine.hpp

cpp_files = \
analytic_cont_geom_av_price.cpp \
Expand All @@ -24,7 +25,8 @@ cpp_files = \
mc_discr_arith_av_price_heston.cpp \
mc_discr_arith_av_strike.cpp \
mc_discr_geom_av_price.cpp \
mc_discr_geom_av_price_heston.cpp
mc_discr_geom_av_price_heston.cpp \
turnbullwakemanasianengine.cpp

if UNITY_BUILD

Expand Down
126 changes: 126 additions & 0 deletions ql/pricingengines/asian/turnbullwakemanasianengine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ)
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
<[email protected]>. The license is also available online at
<http://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/exercise.hpp>
#include <ql/pricingengines/asian/turnbullwakemanasianengine.hpp>
#include <ql/pricingengines/blackformula.hpp>

using namespace QuantLib;

void TurnbullWakemanAsianEngine::calculate() const {

// Enforce a few required things
QL_REQUIRE(arguments_.exercise->type() == Exercise::European, "not a European Option");
QL_REQUIRE(arguments_.averageType == Average::Type::Arithmetic,
"must be Arithmetic Average::Type");

// Calculate the accrued portion
Size pastFixings = arguments_.pastFixings;
Size futureFixings = arguments_.fixingDates.size();
Real accruedAverage = 0;
if (pastFixings != 0) {
accruedAverage = arguments_.runningAccumulator / (pastFixings + futureFixings);
}
results_.additionalResults["accrued"] = accruedAverage;

Real discount = process_->riskFreeRate()->discount(arguments_.exercise->lastDate());
results_.additionalResults["discount"] = discount;

ext::shared_ptr<PlainVanillaPayoff> payoff =
ext::dynamic_pointer_cast<PlainVanillaPayoff>(arguments_.payoff);
QL_REQUIRE(payoff, "non-plain payoff given");

// We will read the volatility off the surface at the effective strike
Real effectiveStrike = payoff->strike() - accruedAverage;
results_.additionalResults["strike"] = payoff->strike();
results_.additionalResults["effective_strike"] = effectiveStrike;

// If the effective strike is negative, exercise resp. permanent OTM is guaranteed and the
// valuation is made easy
Size m = futureFixings + pastFixings;
if (effectiveStrike <= 0.0) {
// For a reference, see "Option Pricing Formulas", Haug, 2nd ed, p. 193
if (payoff->optionType() == Option::Type::Call) {
Real spot = process_->stateVariable()->value();
Real S_A_hat = accruedAverage;
for (const auto& fd : arguments_.fixingDates) {
S_A_hat += (spot * process_->dividendYield()->discount(fd) /
process_->riskFreeRate()->discount(fd)) /
m;
}
results_.value = discount * (S_A_hat - payoff->strike());
} else if (payoff->optionType() == Option::Type::Put) {
results_.value = 0;
}
return;
}

// We should only get this far when the effectiveStrike > 0 but will check anyway
QL_REQUIRE(effectiveStrike > 0.0, "expected effectiveStrike to be positive");

// Expected value of the non-accrued portion of the average prices
// In general, m will equal n below if there is no accrued. If accrued, m > n.
Real EA = 0.0;
std::vector<Real> forwards;
std::vector<Time> times;
std::vector<Real> spotVars;
std::vector<Real> spotVolsVec; // additional results only
Real spot = process_->stateVariable()->value();

for (const auto& fd : arguments_.fixingDates) {
DiscountFactor dividendDiscount = process_->dividendYield()->discount(fd);
DiscountFactor riskFreeDiscountForFwdEstimation = process_->riskFreeRate()->discount(fd);

forwards.push_back(spot * dividendDiscount / riskFreeDiscountForFwdEstimation);
times.push_back(process_->blackVolatility()->timeFromReference(fd));

spotVars.push_back(
process_->blackVolatility()->blackVariance(times.back(), effectiveStrike));
spotVolsVec.push_back(std::sqrt(spotVars.back() / times.back()));

EA += forwards.back();
}
EA /= m;

// Expected value of A^2.
Real EA2 = 0.0;
Size n = forwards.size();

for (Size i = 0; i < n; ++i) {
EA2 += forwards[i] * forwards[i] * exp(spotVars[i]);
for (Size j = 0; j < i; ++j) {
EA2 += 2 * forwards[i] * forwards[j] * exp(spotVars[j]);
}
}

EA2 /= m * m;

// Calculate value
Real tn = times.back();
Real sigma = sqrt(log(EA2 / (EA * EA)) / tn);

// Populate results
results_.value =
blackFormula(payoff->optionType(), effectiveStrike, EA, sigma * sqrt(tn), discount);
results_.additionalResults["forward"] = EA;
results_.additionalResults["exp_A_2"] = EA2;
results_.additionalResults["tte"] = tn;
results_.additionalResults["sigma"] = sigma;
results_.additionalResults["times"] = times;
results_.additionalResults["spotVols"] = spotVolsVec;
results_.additionalResults["forwards"] = forwards;
}
62 changes: 62 additions & 0 deletions ql/pricingengines/asian/turnbullwakemanasianengine.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ)
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
<[email protected]>. The license is also available online at
<http://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.
*/

/*! \file ql/pricingengines/asian/turnbullwakemanasianengine.hpp
\brief Turnbull Wakeman moment-matching Asian option Engine
\ingroup asianengines
*/

#ifndef quantlib_turnbull_wakeman_asian_engine_hpp
#define quantlib_turnbull_wakeman_asian_engine_hpp

#include <ql/instruments/asianoption.hpp>
#include <ql/processes/blackscholesprocess.hpp>

namespace QuantLib {

/*! Turnbull Wakeman two moment-matching Asian option Engine
Analytical pricing based on the two-moment Turnbull-Wakeman
approximation.
References: "Commodity Option Pricing", Iain Clark, Wiley, section 2.7.4.
"Option Pricing Formulas, Second Edition", E.G. Haug, 2006, pp. 192-202.
Some parts of the implementation were modeled after calculations from the
CommodityAveragePriceOptionAnalyticalEngine class in Open Source Risk Engine
(https://github.com/OpenSourceRisk/Engine).
\test
- the correctness of the returned value is tested by reproducing
results in literature with flat as well as upward and downward
sloping volatility term structures.
- the pricing of trades with guaranteed exercise/OTM is also tested.
*/
class TurnbullWakemanAsianEngine : public DiscreteAveragingAsianOption::engine {
public:
explicit TurnbullWakemanAsianEngine(
ext::shared_ptr<GeneralizedBlackScholesProcess> process)
: process_(process) {
registerWith(process_);
}

void calculate() const;

private:
ext::shared_ptr<GeneralizedBlackScholesProcess> process_;
};

}

#endif
Loading

0 comments on commit 30121a0

Please sign in to comment.