19#include <boost/make_shared.hpp>
20#include <boost/test/unit_test.hpp>
26#include <oret/toplevelfixture.hpp>
27#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
28#include <ql/termstructures/yield/flatforward.hpp>
29#include <ql/time/daycounters/actual360.hpp>
30#include <ql/time/daycounters/actualactual.hpp>
33using namespace boost::unit_test_framework;
43 asof_ = Date(3, Feb, 2016);
50 std::map<std::string, QuantLib::Handle<QuantLib::Quote>> quotes;
51 quotes[
"EURUSD"] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.2));
52 fx_ = QuantLib::ext::make_shared<FXTriangulation>(quotes);
55 auto conventions = QuantLib::ext::make_shared<Conventions>();
56 conventions->add(QuantLib::ext::make_shared<FXConvention>(
"EUR-USD-FX",
"0",
"EUR",
"USD",
"10000",
"EUR,USD"));
57 InstrumentConventions::instance().setConventions(conventions);
62 TestMarket(Real spot, Real q, Real r, Real vol,
bool withFixings =
false) :
MarketImpl(false) {
63 asof_ = Date(3, Feb, 2016);
67 flatRateYts(r, Actual360());
69 flatRateYts(q, Actual360());
72 std::map<std::string, QuantLib::Handle<QuantLib::Quote>> quotes;
73 quotes[
"JPYEUR"] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot));
74 fx_ = QuantLib::ext::make_shared<FXTriangulation>(quotes);
77 auto conventions = QuantLib::ext::make_shared<Conventions>();
78 conventions->add(QuantLib::ext::make_shared<FXConvention>(
"EUR-JPY-FX",
"0",
"EUR",
"JPY",
"10000",
"EUR,JPY"));
79 InstrumentConventions::instance().setConventions(conventions);
86 TimeSeries<Real> pastFixings;
87 pastFixings[Date(1, Feb, 2016)] = 100;
88 pastFixings[Date(2, Feb, 2016)] = 90;
89 IndexManager::instance().setHistory(
"FX/Reuters JPY/EUR", pastFixings);
94 Handle<YieldTermStructure> flatRateYts(Real forward,
const DayCounter& dc = ActualActual(ActualActual::ISDA)) {
95 QuantLib::ext::shared_ptr<YieldTermStructure> yts(
new FlatForward(0, NullCalendar(), forward, dc));
96 return Handle<YieldTermStructure>(yts);
98 Handle<BlackVolTermStructure> flatRateFxv(Volatility forward,
const DayCounter& dc = ActualActual(ActualActual::ISDA)) {
99 QuantLib::ext::shared_ptr<BlackVolTermStructure> fxv(
new BlackConstantVol(0, NullCalendar(), forward, dc));
100 return Handle<BlackVolTermStructure>(fxv);
105BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
107BOOST_AUTO_TEST_SUITE(FXOptionTests)
112 BOOST_TEST_MESSAGE(
"Testing FXOption Price...");
114 Date today = Settings::instance().evaluationDate();
117 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>();
118 Settings::instance().evaluationDate() = market->asofDate();
121 OptionData optionData(
"Long",
"Call",
"European",
true, vector<string>(1,
"20170203"));
122 OptionData optionDataPremiumUSD(
"Long",
"Call",
"European",
true, vector<string>(1,
"20170203"),
"Cash",
"",
124 OptionData optionDataPremiumEUR(
"Long",
"Call",
"European",
true, vector<string>(1,
"20170203"),
"Cash",
"",
127 FxOption fxOption(env, optionData,
"EUR", 1000000,
129 FxOption fxOptionPremiumUSD(env, optionDataPremiumUSD,
"EUR", 1000000,
"USD", 1250000);
130 FxOption fxOptionPremiumEUR(env, optionDataPremiumEUR,
"EUR", 1000000,
"USD", 1250000);
134 Real expectedNPV_USD = 29148.0;
135 Real expectedNPV_EUR = 24290.0;
136 Real expectedNPV_USD_Premium_USD = 19495.6;
137 Real expectedNPV_USD_Premium_EUR = 17496.4;
140 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
141 engineData->model(
"FxOption") =
"GarmanKohlhagen";
142 engineData->engine(
"FxOption") =
"AnalyticEuropeanEngine";
143 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
145 fxOption.
build(engineFactory);
146 fxOptionPremiumUSD.
build(engineFactory);
147 fxOptionPremiumEUR.
build(engineFactory);
150 Real npv_prem_usd = fxOptionPremiumUSD.
instrument()->NPV();
151 Real npv_prem_eur = fxOptionPremiumEUR.
instrument()->NPV();
153 BOOST_TEST_MESSAGE(
"FX Option, NPV Currency " << fxOption.
npvCurrency());
154 BOOST_TEST_MESSAGE(
"NPV = " << npv);
155 BOOST_TEST_MESSAGE(
"NPV with premium in USD = " << npv_prem_usd);
156 BOOST_TEST_MESSAGE(
"NPV with premium in EUR = " << npv_prem_eur);
160 QL_REQUIRE(fxOption.
npvCurrency() ==
"USD",
"unexpected NPV currency");
162 BOOST_CHECK_CLOSE(npv, expectedNPV_EUR, 0.2);
164 BOOST_CHECK_CLOSE(npv, expectedNPV_USD, 0.2);
165 BOOST_CHECK_CLOSE(npv_prem_usd, expectedNPV_USD_Premium_USD, 0.001);
166 BOOST_CHECK_CLOSE(npv_prem_eur, expectedNPV_USD_Premium_EUR, 0.001);
168 BOOST_FAIL(
"Unexpected FX Option npv currency " << fxOption.
npvCurrency());
171 Settings::instance().evaluationDate() = today;
176struct AmericanOptionData {
191 BOOST_TEST_MESSAGE(
"Testing FXAmericanOption Price...");
193 AmericanOptionData fxd[] = {
194 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.15, 0.0206},
195 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.15, 1.8771},
196 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.15, 10.0089},
197 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.25, 0.3159},
198 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.25, 3.1280},
199 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 10.3919},
200 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.10, 0.35, 0.9495},
201 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.10, 0.35, 4.3777},
202 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 11.1679},
203 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.15, 0.8208},
204 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.15, 4.0842},
205 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 10.8087},
206 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.25, 2.7437},
207 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.25, 6.8015},
208 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 13.0170},
209 {Option::Call, 100.00, 90.00, 0.10, 0.10, 0.50, 0.35, 5.0063},
210 {Option::Call, 100.00, 100.00, 0.10, 0.10, 0.50, 0.35, 9.5106},
211 {Option::Call, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 15.5689},
212 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.15, 10.0000},
213 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.15, 1.8770},
214 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.15, 0.0410},
215 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.25, 10.2533},
216 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.25, 3.1277},
217 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.25, 0.4562},
218 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.10, 0.35, 10.8787},
219 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.10, 0.35, 4.3777},
220 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.10, 0.35, 1.2402},
221 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.15, 10.5595},
222 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.15, 4.0842},
223 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.15, 1.0822},
224 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.25, 12.4419},
225 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.25, 6.8014},
226 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.25, 3.3226},
227 {Option::Put, 100.00, 90.00, 0.10, 0.10, 0.50, 0.35, 14.6945},
228 {Option::Put, 100.00, 100.00, 0.10, 0.10, 0.50, 0.35, 9.5104},
229 {Option::Put, 100.00, 110.00, 0.10, 0.10, 0.50, 0.35, 5.8823},
230 {Option::Put, 100.00, 100.00, 0.00, 0.00, 0.50, 0.15, 4.2294}};
232 for (
auto& f : fxd) {
234 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
235 Date today = Settings::instance().evaluationDate();
236 Settings::instance().evaluationDate() = market->asofDate();
239 string maturityDate =
to_string(market->asofDate() + Integer(f.t * 360 + 0.5));
240 OptionData optionData(
"Long", f.type == Option::Call ?
"Call" :
"Put",
"American",
false,
241 vector<string>(1, maturityDate));
243 FxOption fxOption(env, optionData,
"JPY", 1,
246 Real expectedNPV = f.result;
249 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
250 engineData->model(
"FxOptionAmerican") =
"GarmanKohlhagen";
251 engineData->engine(
"FxOptionAmerican") =
"BaroneAdesiWhaleyApproximationEngine";
253 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
255 fxOption.
build(engineFactory);
259 BOOST_TEST_MESSAGE(
"FX American Option, NPV Currency " << fxOption.
npvCurrency());
260 BOOST_TEST_MESSAGE(
"NPV = " << npv);
263 QL_REQUIRE(fxOption.
npvCurrency() ==
"EUR",
"unexpected NPV currency ");
265 BOOST_CHECK_CLOSE(npv, expectedNPV, 0.2);
266 Settings::instance().evaluationDate() = today;
272 BOOST_TEST_MESSAGE(
"Testing finite-difference engine "
273 "for American options...");
280 AmericanOptionData juValues[] = {
282 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.0833, 0.2, 0.006},
283 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.3333, 0.2, 0.201},
284 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.5833, 0.2, 0.433},
286 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.0833, 0.2, 0.851},
287 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.3333, 0.2, 1.576},
288 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.5833, 0.2, 1.984},
290 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.0833, 0.2, 5.000},
291 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.3333, 0.2, 5.084},
292 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.5833, 0.2, 5.260},
294 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.0833, 0.3, 0.078},
295 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.3333, 0.3, 0.697},
296 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.5833, 0.3, 1.218},
298 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.0833, 0.3, 1.309},
299 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.3333, 0.3, 2.477},
300 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.5833, 0.3, 3.161},
302 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.0833, 0.3, 5.059},
303 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.3333, 0.3, 5.699},
304 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.5833, 0.3, 6.231},
306 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.0833, 0.4, 0.247},
307 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.3333, 0.4, 1.344},
308 {Option::Put, 35.00, 40.00, 0.0, 0.0488, 0.5833, 0.4, 2.150},
310 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.0833, 0.4, 1.767},
311 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.3333, 0.4, 3.381},
312 {Option::Put, 40.00, 40.00, 0.0, 0.0488, 0.5833, 0.4, 4.342},
314 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.0833, 0.4, 5.288},
315 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.3333, 0.4, 6.501},
316 {Option::Put, 45.00, 40.00, 0.0, 0.0488, 0.5833, 0.4, 7.367},
322 {Option::Call, 100.00, 80.00, 0.07, 0.03, 3.0, 0.2, 2.605},
323 {Option::Call, 100.00, 90.00, 0.07, 0.03, 3.0, 0.2, 5.182},
324 {Option::Call, 100.00, 100.00, 0.07, 0.03, 3.0, 0.2, 9.065},
325 {Option::Call, 100.00, 110.00, 0.07, 0.03, 3.0, 0.2, 14.430},
326 {Option::Call, 100.00, 120.00, 0.07, 0.03, 3.0, 0.2, 21.398},
328 {Option::Call, 100.00, 80.00, 0.07, 0.03, 3.0, 0.4, 11.336},
329 {Option::Call, 100.00, 90.00, 0.07, 0.03, 3.0, 0.4, 15.711},
330 {Option::Call, 100.00, 100.00, 0.07, 0.03, 3.0, 0.4, 20.760},
331 {Option::Call, 100.00, 110.00, 0.07, 0.03, 3.0, 0.4, 26.440},
332 {Option::Call, 100.00, 120.00, 0.07, 0.03, 3.0, 0.4, 32.709},
334 {Option::Call, 100.00, 80.00, 0.07, 0.00001, 3.0, 0.3, 5.552},
335 {Option::Call, 100.00, 90.00, 0.07, 0.00001, 3.0, 0.3, 8.868},
336 {Option::Call, 100.00, 100.00, 0.07, 0.00001, 3.0, 0.3, 13.158},
337 {Option::Call, 100.00, 110.00, 0.07, 0.00001, 3.0, 0.3, 18.458},
338 {Option::Call, 100.00, 120.00, 0.07, 0.00001, 3.0, 0.3, 24.786},
340 {Option::Call, 100.00, 80.00, 0.03, 0.07, 3.0, 0.3, 12.177},
341 {Option::Call, 100.00, 90.00, 0.03, 0.07, 3.0, 0.3, 17.411},
342 {Option::Call, 100.00, 100.00, 0.03, 0.07, 3.0, 0.3, 23.402},
343 {Option::Call, 100.00, 110.00, 0.03, 0.07, 3.0, 0.3, 30.028},
344 {Option::Call, 100.00, 120.00, 0.03, 0.07, 3.0, 0.3, 37.177}};
346 Real tolerance = 8.0e-2;
348 for (
auto& f : juValues) {
350 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
351 Date today = Settings::instance().evaluationDate();
352 Settings::instance().evaluationDate() = market->asofDate();
355 string maturityDate =
to_string(market->asofDate() + Integer(f.t * 360 + 0.5));
356 OptionData optionData(
"Long", f.type == Option::Call ?
"Call" :
"Put",
"American",
false,
357 vector<string>(1, maturityDate));
359 FxOption fxOption(env, optionData,
"JPY", 1,
362 Real expectedNPV = f.result;
365 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
366 engineData->model(
"FxOptionAmerican") =
"GarmanKohlhagen";
367 engineData->engine(
"FxOptionAmerican") =
"FdBlackScholesVanillaEngine";
368 engineData->engineParameters(
"FxOptionAmerican") = {
369 {
"Scheme",
"Douglas"}, {
"TimeGridPerYear",
"100"}, {
"XGrid",
"100"}, {
"DampingSteps",
"0"}};
371 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
373 fxOption.
build(engineFactory);
377 BOOST_TEST_MESSAGE(
"FX American Option, NPV Currency " << fxOption.
npvCurrency());
378 BOOST_TEST_MESSAGE(
"NPV = " << npv);
381 QL_REQUIRE(fxOption.
npvCurrency() ==
"EUR",
"unexpected NPV currency ");
383 BOOST_CHECK_SMALL(npv - expectedNPV, tolerance);
384 Settings::instance().evaluationDate() = today;
388BOOST_AUTO_TEST_SUITE_END()
390BOOST_AUTO_TEST_SUITE_END()
Engine builder for FX Options.
Serializable object holding generic trade data, reporting dimensions.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
static const string defaultConfiguration
Default configuration label.
map< pair< string, string >, Handle< BlackVolTermStructure > > fxVols_
QuantLib::ext::shared_ptr< FXTriangulation > fx_
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
Serializable object holding option data.
Serializable object holding premium data.
const QuantLib::ext::shared_ptr< InstrumentWrapper > & instrument() const
const string & npvCurrency() const
A class to hold pricing engine parameters.
FX Option data model and serialization.
An implementation of the Market class that stores the required objects in maps.
std::string to_string(const LocationInfo &l)
BOOST_AUTO_TEST_CASE(testFXOptionPrice)
string conversion utilities