Skip to content

Commit

Permalink
added Choi Asian engine (#2129)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Jan 7, 2025
2 parents fec2aa0 + cf76bc0 commit 541aa26
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 21 deletions.
4 changes: 3 additions & 1 deletion QuantLib.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1476,6 +1476,7 @@
<ClInclude Include="ql\pricingengines\asian\analytic_cont_geom_av_price.hpp" />
<ClInclude Include="ql\pricingengines\asian\analytic_discr_geom_av_price.hpp" />
<ClInclude Include="ql\pricingengines\asian\analytic_discr_geom_av_strike.hpp" />
<ClInclude Include="ql\pricingengines\asian\choiasianengine.hpp" />
<ClInclude Include="ql\pricingengines\asian\fdblackscholesasianengine.hpp" />
<ClInclude Include="ql\pricingengines\asian\mc_discr_arith_av_price.hpp" />
<ClInclude Include="ql\pricingengines\asian\mc_discr_arith_av_price_heston.hpp" />
Expand Down Expand Up @@ -2550,6 +2551,7 @@
<ClCompile Include="ql\pricingengines\asian\analytic_cont_geom_av_price.cpp" />
<ClCompile Include="ql\pricingengines\asian\analytic_discr_geom_av_price.cpp" />
<ClCompile Include="ql\pricingengines\asian\analytic_discr_geom_av_strike.cpp" />
<ClCompile Include="ql\pricingengines\asian\choiasianengine.cpp" />
<ClCompile Include="ql\pricingengines\asian\fdblackscholesasianengine.cpp" />
<ClCompile Include="ql\pricingengines\asian\mc_discr_arith_av_price.cpp" />
<ClCompile Include="ql\pricingengines\asian\mc_discr_arith_av_price_heston.cpp" />
Expand Down Expand Up @@ -2851,4 +2853,4 @@
<Import Project=".\Build.props" Condition="Exists('.\Build.props')" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>
6 changes: 6 additions & 0 deletions QuantLib.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -3867,6 +3867,9 @@
<ClInclude Include="ql\pricingengines\asian\fdblackscholesasianengine.hpp">
<Filter>pricingengines\asian</Filter>
</ClInclude>
<ClInclude Include="ql\pricingengines\asian\choiasianengine.hpp">
<Filter>pricingengines\asian</Filter>
</ClInclude>
<ClInclude Include="ql\pricingengines\barrier\fdblackscholesbarrierengine.hpp">
<Filter>pricingengines\barrier</Filter>
</ClInclude>
Expand Down Expand Up @@ -6750,6 +6753,9 @@
<ClCompile Include="ql\pricingengines\vanilla\fdbatesvanillaengine.cpp">
<Filter>pricingengines\vanilla</Filter>
</ClCompile>
<ClCompile Include="ql\pricingengines\asian\choiasianengine.cpp">
<Filter>pricingengines\asian</Filter>
</ClCompile>
<ClCompile Include="ql\pricingengines\asian\fdblackscholesasianengine.cpp">
<Filter>pricingengines\asian</Filter>
</ClCompile>
Expand Down
2 changes: 2 additions & 0 deletions ql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ set(QL_SOURCES
pricingengines/asian/analytic_cont_geom_av_price.cpp
pricingengines/asian/analytic_discr_geom_av_price.cpp
pricingengines/asian/analytic_discr_geom_av_strike.cpp
pricingengines/asian/choiasianengine.cpp
pricingengines/asian/fdblackscholesasianengine.cpp
pricingengines/asian/mc_discr_arith_av_price.cpp
pricingengines/asian/mc_discr_arith_av_price_heston.cpp
Expand Down Expand Up @@ -1858,6 +1859,7 @@ set(QL_HEADERS
pricingengines/asian/analytic_cont_geom_av_price.hpp
pricingengines/asian/analytic_discr_geom_av_price.hpp
pricingengines/asian/analytic_discr_geom_av_strike.hpp
pricingengines/asian/choiasianengine.hpp
pricingengines/asian/fdblackscholesasianengine.hpp
pricingengines/asian/mc_discr_arith_av_price.hpp
pricingengines/asian/mc_discr_arith_av_price_heston.hpp
Expand Down
2 changes: 2 additions & 0 deletions ql/pricingengines/asian/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ this_include_HEADERS = \
analytic_cont_geom_av_price.hpp \
analytic_discr_geom_av_price.hpp \
analytic_discr_geom_av_strike.hpp \
choiasianengine.hpp \
fdblackscholesasianengine.hpp \
mc_discr_arith_av_price.hpp \
mc_discr_arith_av_price_heston.hpp \
Expand All @@ -20,6 +21,7 @@ cpp_files = \
analytic_cont_geom_av_price.cpp \
analytic_discr_geom_av_price.cpp \
analytic_discr_geom_av_strike.cpp \
choiasianengine.cpp \
fdblackscholesasianengine.cpp \
mc_discr_arith_av_price.cpp \
mc_discr_arith_av_price_heston.cpp \
Expand Down
1 change: 1 addition & 0 deletions ql/pricingengines/asian/all.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ql/pricingengines/asian/analytic_cont_geom_av_price.hpp>
#include <ql/pricingengines/asian/analytic_discr_geom_av_price.hpp>
#include <ql/pricingengines/asian/analytic_discr_geom_av_strike.hpp>
#include <ql/pricingengines/asian/choiasianengine.hpp>
#include <ql/pricingengines/asian/fdblackscholesasianengine.hpp>
#include <ql/pricingengines/asian/mc_discr_arith_av_price.hpp>
#include <ql/pricingengines/asian/mc_discr_arith_av_price_heston.hpp>
Expand Down
168 changes: 168 additions & 0 deletions ql/pricingengines/asian/choiasianengine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
Copyright (C) 2025 Klaus Spanderen
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
<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/quotes/simplequote.hpp>
#include <ql/instruments/basketoption.hpp>
#include <ql/pricingengines/blackformula.hpp>
#include <ql/pricingengines/asian/choiasianengine.hpp>
#include <ql/pricingengines/basket/choibasketengine.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>

namespace QuantLib {

ChoiAsianEngine::ChoiAsianEngine(
ext::shared_ptr<GeneralizedBlackScholesProcess> process,
Real lambda,
Size maxNrIntegrationSteps)
: process_(std::move(process)),
lambda_(lambda),
maxNrIntegrationSteps_(maxNrIntegrationSteps) {
registerWith(process_);
}

void ChoiAsianEngine::calculate() const {
QL_REQUIRE(arguments_.averageType == Average::Type::Arithmetic,
"must be Average::Type Arithmetic ");

QL_REQUIRE(arguments_.exercise->type() == Exercise::European,
"not a European Option");

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

std::vector<Date> fixingDates = arguments_.fixingDates;
std::sort(fixingDates.begin(), fixingDates.end());

Size futureFixings = fixingDates.size();
Size pastFixings = arguments_.pastFixings;
Real runningAccumulator = arguments_.runningAccumulator;

const Date exerciseDate = arguments_.exercise->lastDate();
const Handle<YieldTermStructure> rTS = process_->riskFreeRate();

if ( futureFixings > 0
&& process_->time(fixingDates.front()) == Time(0)) {
// push today fixing to past fixings
fixingDates.erase(fixingDates.begin());
futureFixings--;
pastFixings++;
runningAccumulator += process_->x0();
}

if (futureFixings == 0) {
QL_REQUIRE(pastFixings > 0, "no past fixings given");
results_.value = (*payoff)(runningAccumulator/pastFixings)
* rTS->discount(exerciseDate);

return;
}

QL_REQUIRE(fixingDates.back() <= exerciseDate,
"last fixing date must be before exercise date");
QL_REQUIRE(process_->time(fixingDates.front()) >= 0.0,
"first fixing date is in the past");

QL_REQUIRE(std::adjacent_find(fixingDates.begin(), fixingDates.end())
== fixingDates.end(), "two fixing dates are the same");

const Real accruedAverage = (pastFixings != 0)
? runningAccumulator / (pastFixings + futureFixings)
: 0.0;

const Real strike = payoff->strike() - accruedAverage;
QL_REQUIRE(strike >= 0.0, "effective strike should to be positive");

const Handle<YieldTermStructure> qTS = process_->dividendYield();
const Handle<BlackVolTermStructure> volTS = process_->blackVolatility();
const Date volRefDate = volTS->referenceDate();
const DayCounter volDc = volTS->dayCounter();

if (futureFixings > 1) {
std::vector<Time> fixingTimes(futureFixings), variances(futureFixings);
for (Size i=0; i < futureFixings; ++i) {
const Date& fixingDate = fixingDates[i];
fixingTimes[i] = volDc.yearFraction(volRefDate, fixingDate);
variances[i] = process_->blackVolatility()->blackVariance(fixingDate, strike);
}

Matrix rho(futureFixings, futureFixings);
for (Size i=0; i < rho.rows(); ++i)
for (Size j=i; j < rho.columns(); ++j)
rho[i][j] = rho[j][i] =
variances[std::min(i,j)] / std::sqrt(variances[i]*variances[j]);

const Handle<YieldTermStructure> zeroTS(
ext::make_shared<FlatForward>(rTS->referenceDate(), 0.0, rTS->dayCounter())
);

std::vector<ext::shared_ptr<GeneralizedBlackScholesProcess> > processes;
processes.reserve(futureFixings);
for (Size i=0; i < futureFixings; ++i) {
const Date& fixingDate = fixingDates[i];
const Volatility sig = volTS->blackVol(fixingDate, payoff->strike())
* std::sqrt(fixingTimes[i]/fixingTimes.back());

processes.emplace_back(
ext::make_shared<GeneralizedBlackScholesProcess>(
Handle<Quote>(
ext::make_shared<SimpleQuote>(
process_->x0()*qTS->discount(fixingDate)/rTS->discount(fixingDate)
)
),
zeroTS, zeroTS,
Handle<BlackVolTermStructure>(
ext::make_shared<BlackConstantVol>(
volRefDate, volTS->calendar(),
Handle<Quote>(ext::make_shared<SimpleQuote>(sig)),
volDc
)
)
)
);
}

BasketOption basketOption(
ext::make_shared<AverageBasketPayoff>(
ext::make_shared<PlainVanillaPayoff>(payoff->optionType(), strike),
Array(futureFixings, 1.0/(futureFixings + pastFixings))
),
ext::make_shared<EuropeanExercise>(fixingDates.back())
);
basketOption.setPricingEngine(
ext::make_shared<ChoiBasketEngine>(
processes, rho, lambda_, maxNrIntegrationSteps_)
);

results_.value = basketOption.NPV() * rTS->discount(exerciseDate);
}
else if (futureFixings == 1) {
results_.value = blackFormula(
payoff->optionType(),
strike,
process_->x0()/(pastFixings + futureFixings)
*qTS->discount(fixingDates.back())/rTS->discount(fixingDates.back()),
std::sqrt(volTS->blackVariance(fixingDates.back(), strike)),
rTS->discount(exerciseDate)
);
}
}
}
65 changes: 65 additions & 0 deletions ql/pricingengines/asian/choiasianengine.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
Copyright (C) 2025 Klaus Spanderen
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
<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 choiasianengine.hpp
\brief Black Scholes arithmetic Asian option engine
*/

#ifndef quantlib_choi_asian_engine_hpp
#define quantlib_choi_asian_engine_hpp

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

namespace QuantLib {
//! Pricing engine for arithmetic Asian options
/*! This class replicates an arithmetic Asian option using a basket option.
The pricing of an arithmetic Asian option is substituted with the pricing
of a basket option.
*/

/*! References:
"Sum of all Black-Scholes-Merton Models: An efficient Pricing Method for
Spread, Basket and Asian Options", Jaehyuk Choi, 2018
https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2913048
A Python implementation from the author of the paper is also available
https://github.com/PyFE/PyFENG
\ingroup asianengines
*/

class ChoiAsianEngine : public DiscreteAveragingAsianOption::engine {
public:
explicit ChoiAsianEngine(
ext::shared_ptr<GeneralizedBlackScholesProcess> p,
Real lambda = 15,
Size maxNrIntegrationSteps = 2 << 21);

void calculate() const override;

private:
const ext::shared_ptr<GeneralizedBlackScholesProcess> process_;
const Real lambda_;
const Size maxNrIntegrationSteps_;
};
}

#endif
2 changes: 1 addition & 1 deletion ql/pricingengines/asian/fdblackscholesasianengine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace QuantLib {

//! Finite-Differences Black Scholes arithmetic asian option engine

/*! \ingroup vanillaengines
/*! \ingroup asianengines
*/

class GeneralizedBlackScholesProcess;
Expand Down
2 changes: 1 addition & 1 deletion ql/time/daycounters/yearfractiontodate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

#include <ql/math/comparison.hpp>
#include "ql/time/daycounters/yearfractiontodate.hpp"
#include <ql/time/daycounters/yearfractiontodate.hpp>

#include <boost/numeric/conversion/cast.hpp>
#include <cmath>
Expand Down
Loading

0 comments on commit 541aa26

Please sign in to comment.