Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fxexotics.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2017 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 <boost/make_shared.hpp>
20#include <boost/test/unit_test.hpp>
43#include <oret/toplevelfixture.hpp>
44#include <ql/cashflows/simplecashflow.hpp>
45#include <ql/currencies/all.hpp>
46#include <ql/indexes/indexmanager.hpp>
47#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
48#include <ql/termstructures/yield/flatforward.hpp>
49#include <ql/time/calendars/target.hpp>
50#include <ql/time/calendars/unitedstates.hpp>
51#include <ql/time/daycounters/actual360.hpp>
53
54using namespace QuantLib;
55using namespace boost::unit_test_framework;
56using namespace std;
57using namespace ore::data;
58
59BOOST_FIXTURE_TEST_SUITE(OREPlusEquityFXTestSuite, ore::test::TopLevelFixture)
60
61BOOST_AUTO_TEST_SUITE(FXOptionTest)
62
63namespace {
64
65struct FxOptionData {
66 string optionType;
67 Real s; // spot
68 Real k; // strike
69 Rate q; // dividend
70 Rate r; // risk-free rate
71 string t; // time to maturity
72 Volatility v; // volatility
73 Real result; // expected result
74};
75
76class TestMarket : public MarketImpl {
77public:
78 TestMarket(Real spot, Real q, Real r, Real vol, bool withFixings = false) : MarketImpl(false) {
79 asof_ = Date(3, Feb, 2016);
80
81 Settings::instance().evaluationDate() = asof_;
82
83 // build discount
84 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "EUR")] = flatRateYts(q);
85 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "JPY")] = flatRateYts(r);
86
87 // add fx rates
88 std::map<std::string, QuantLib::Handle<QuantLib::Quote>> quotes;
89 quotes["EURJPY"] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot));
90 fx_ = QuantLib::ext::make_shared<FXTriangulation>(quotes);
91
92 // add fx conventions
93 auto conventions = QuantLib::ext::make_shared<Conventions>();
94 conventions->add(QuantLib::ext::make_shared<FXConvention>("EUR-JPY-FX", "0", "EUR", "JPY", "10000", "EUR,JPY"));
95 InstrumentConventions::instance().setConventions(conventions);
96
97 // build fx vols
98 fxVols_[make_pair(Market::defaultConfiguration, "EURJPY")] = flatRateFxv(vol);
99
100 if (withFixings) {
101 // add fixings
102 TimeSeries<Real> pastFixings;
103 pastFixings[Date(1, Feb, 2016)] = 100;
104 pastFixings[Date(2, Feb, 2016)] = 90;
105 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
106 TimeSeries<Real> pastFixingsInverted;
107 pastFixingsInverted[Date(1, Feb, 2016)] = 1 / pastFixings[Date(1, Feb, 2016)];
108 pastFixingsInverted[Date(2, Feb, 2016)] = 1 / pastFixings[Date(2, Feb, 2016)];
109 IndexManager::instance().setHistory("Reuters JPY/EUR", pastFixingsInverted);
110 }
111 }
112
113 void setFxSpot(string ccyPair, Real spot) {
114 auto q = QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(fx_->getQuote(ccyPair).currentLink());
115 QL_REQUIRE(q, "internal error: could not cast quote to SimpleQuote in TestMarket::setFxSpot()");
116 q->setValue(spot);
117 }
118
119private:
120 Handle<YieldTermStructure> flatRateYts(Real forward) {
121 QuantLib::ext::shared_ptr<YieldTermStructure> yts(new FlatForward(0, NullCalendar(), forward, Actual360()));
122 return Handle<YieldTermStructure>(yts);
123 }
124 Handle<BlackVolTermStructure> flatRateFxv(Volatility forward) {
125 QuantLib::ext::shared_ptr<BlackVolTermStructure> fxv(new BlackConstantVol(0, NullCalendar(), forward, Actual360()));
126 return Handle<BlackVolTermStructure>(fxv);
127 }
128
129 QuantLib::ext::shared_ptr<SimpleQuote> fxSpot_;
130};
131} // namespace
132
133// FX Digital Option test, "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 88
134// type, strike, spot, q, r, t, vol, value
135// "Put", 80.00, 100.0, 0.06, 0.06, 0.75, 0.35, 2.6710
136BOOST_AUTO_TEST_CASE(testFXDigitalOptionPrice) {
137 BOOST_TEST_MESSAGE("Testing FXDigitalOption Price...");
138
139 FxOptionData fxd[] = {{"Put", 100.00, 80.0, 0.06, 0.06, "20161030", 0.35, 2.6710}};
140
141 for (auto& f : fxd) {
142 // build market
143 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
144 Date today = Settings::instance().evaluationDate();
145 Settings::instance().evaluationDate() = market->asofDate();
146
147 // build FXDigitalOption - expiry in 9 months
148 // Date exDate = today + Integer(0.75*360+0.5);
149 OptionData optionData("Long", "Put", "European", true, vector<string>(1, f.t));
150 Envelope env("CP1");
151 FxDigitalOption fxOption(env, optionData, f.k, 10, "EUR", // foreign
152 "JPY"); // domestic
153
154 Real expectedNPV = f.result;
155
156 // Build and price
157 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
158 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
159 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
160
161 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
162
163 fxOption.build(engineFactory);
164
165 Real npv = fxOption.instrument()->NPV();
166
167 BOOST_TEST_MESSAGE("FX Option, NPV Currency " << fxOption.npvCurrency());
168 BOOST_TEST_MESSAGE("NPV = " << npv);
169
170 // Check NPV matches expected values.
171 QL_REQUIRE(fxOption.npvCurrency() == "JPY", "unexpected NPV currency ");
172
173 BOOST_CHECK_CLOSE(npv, expectedNPV, 0.2);
174 Settings::instance().evaluationDate() = today; // reset
175 }
176}
177
178struct BarrierOptionData {
179 string barrierType;
180 Real barrier;
181 Real rebate;
182 string optionType; // option type
183 Real k; // spot
184 Real s; // strike
185 Rate q; // dividend
186 Rate r; // risk-free rate
187 Real t; // time to maturity
188 Volatility v; // volatility
189 Real result; // expected result
190};
191
192BOOST_AUTO_TEST_CASE(testFXBarrierOptionPrice) {
193 BOOST_TEST_MESSAGE("Testing FXBarrierOption Price...");
194
195 // Testing option values when barrier untouched
196 // Values from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 72
197 BarrierOptionData fxb[] = {// barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
198 {"DownAndOut", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 9.0246},
199 {"DownAndOut", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 4.8759},
200 {"DownAndOut", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
201 {"DownAndOut", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
202 {"DownAndOut", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
203 {"UpAndOut", 105.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.6789},
204 {"UpAndOut", 105.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 2.3580},
205 {"UpAndOut", 105.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.3453},
206
207 {"DownAndIn", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 7.7627},
208 {"DownAndIn", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 4.0109},
209 {"DownAndIn", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.0576},
210 {"DownAndIn", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 13.8333},
211 {"DownAndIn", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 7.8494},
212 {"DownAndIn", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.9795},
213 {"UpAndIn", 105.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 14.1112},
214 {"UpAndIn", 105.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 8.4482},
215 {"UpAndIn", 105.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 4.5910},
216
217 {"DownAndOut", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 8.8334},
218 {"DownAndOut", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.0285},
219 {"DownAndOut", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.4137},
220 {"DownAndOut", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
221 {"DownAndOut", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
222 {"DownAndOut", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
223 {"UpAndOut", 105.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.6341},
224 {"UpAndOut", 105.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4389},
225 {"UpAndOut", 105.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4315},
226
227 {"DownAndIn", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 9.0093},
228 {"DownAndIn", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 5.1370},
229 {"DownAndIn", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.8517},
230 {"DownAndIn", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 14.8816},
231 {"DownAndIn", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 9.2045},
232 {"DownAndIn", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.3043},
233 {"UpAndIn", 105.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 15.2098},
234 {"UpAndIn", 105.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 9.7278},
235 {"UpAndIn", 105.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.8350},
236
237 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
238 {"DownAndOut", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2798},
239 {"DownAndOut", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2947},
240 {"DownAndOut", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.6252},
241 {"DownAndOut", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
242 {"DownAndOut", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
243 {"DownAndOut", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.0000},
244 {"UpAndOut", 105.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 3.7760},
245 {"UpAndOut", 105.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 5.4932},
246 {"UpAndOut", 105.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 7.5187},
247
248 {"DownAndIn", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.9586},
249 {"DownAndIn", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 6.5677},
250 {"DownAndIn", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 11.9752},
251 {"DownAndIn", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 2.2845},
252 {"DownAndIn", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 5.9085},
253 {"DownAndIn", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 11.6465},
254 {"UpAndIn", 105.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 1.4653},
255 {"UpAndIn", 105.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 3.3721},
256 {"UpAndIn", 105.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 7.0846},
257
258 {"DownAndOut", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4170},
259 {"DownAndOut", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 2.4258},
260 {"DownAndOut", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.6246},
261 {"DownAndOut", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
262 {"DownAndOut", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
263 {"DownAndOut", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 3.0000},
264 {"UpAndOut", 105.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 4.2293},
265 {"UpAndOut", 105.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 5.8032},
266 {"UpAndOut", 105.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 7.5649},
267
268 {"DownAndIn", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.8769},
269 {"DownAndIn", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.7989},
270 {"DownAndIn", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 13.3078},
271 {"DownAndIn", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 3.3328},
272 {"DownAndIn", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 7.2636},
273 {"DownAndIn", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 12.9713},
274 {"UpAndIn", 105.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 2.0658},
275 {"UpAndIn", 105.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 4.4226},
276 {"UpAndIn", 105.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 8.3686},
277
278 // Check 'Out' options return rebate when barrier touched
279 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
280 {"DownAndOut", 95.0, 3.0, "Call", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
281 {"DownAndOut", 95.0, 3.0, "Call", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
282 {"DownAndOut", 100.0, 3.0, "Call", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
283 {"DownAndOut", 100.0, 3.0, "Call", 100, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
284 {"DownAndOut", 100.0, 3.0, "Call", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
285 {"UpAndOut", 105.0, 3.0, "Call", 90, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
286 {"UpAndOut", 105.0, 3.0, "Call", 100, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
287 {"UpAndOut", 105.0, 3.0, "Call", 110, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
288
289 {"DownAndOut", 95.0, 3.0, "Put", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
290 {"DownAndOut", 95.0, 3.0, "Put", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
291 {"DownAndOut", 100.0, 3.0, "Put", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
292 {"DownAndOut", 100.0, 3.0, "Put", 100, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
293 {"DownAndOut", 100.0, 3.0, "Put", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 3.0},
294 {"UpAndOut", 105.0, 3.0, "Put", 90, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
295 {"UpAndOut", 105.0, 3.0, "Put", 100, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
296 {"UpAndOut", 105.0, 3.0, "Put", 110, 110.0, 0.04, 0.08, 0.50, 0.25, 3.0},
297
298 // Check 'In' options return fxOption npv when barrier touched
299 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
300 {"DownAndIn", 95.0, 3.0, "Call", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 7.06448},
301 {"DownAndIn", 95.0, 3.0, "Call", 100, 90.0, 0.04, 0.08, 0.50, 0.25, 3.29945},
302 {"DownAndIn", 95.0, 3.0, "Call", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 1.36007},
303 {"DownAndIn", 100.0, 3.0, "Call", 90, 90.0, 0.04, 0.08, 0.50, 0.25, 7.06448},
304 {"DownAndIn", 100.0, 3.0, "Call", 100, 90.0, 0.04, 0.08, 0.50, 0.25, 3.29945},
305 {"DownAndIn", 100.0, 3.0, "Call", 110, 90.0, 0.04, 0.08, 0.50, 0.25, 1.36007},
306 {"UpAndIn", 105.0, 3.0, "Call", 90, 110.0, 0.04, 0.08, 0.50, 0.25, 22.21500},
307 {"UpAndIn", 105.0, 3.0, "Call", 100, 110.0, 0.04, 0.08, 0.50, 0.25, 14.52180},
308 {"UpAndIn", 105.0, 3.0, "Call", 110, 110.0, 0.04, 0.08, 0.50, 0.25, 8.63437}};
309
310 vector<string> positions = {"Long", "Short"};
311 for (auto& f : fxb) {
312 for (auto& position : positions) {
313 // build market
314 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
315 Date today = Settings::instance().evaluationDate();
316 Settings::instance().evaluationDate() = market->asofDate();
317
318 // build FXBarrierOption - expiry in 6 months
319 OptionData optionData(position, f.optionType, "European", true, vector<string>(1, "20160801"));
320 vector<Real> barriers = {f.barrier};
321 vector<TradeBarrier> tradeBarriers;
322 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
323 BarrierData barrierData(f.barrierType, barriers, f.rebate, tradeBarriers);
324 Envelope env("CP1");
325 FxBarrierOption fxBarrierOption(env, optionData, barrierData, Date(), "", "EUR", 1, // foreign
326 "JPY", f.k); // domestic
327
328 // we'll check that the results scale as expected
329 // scaling the notional and the rebate by a million we should get npv_scaled = 1million * npv
330 Real Notional = 1000000;
331 BarrierData barrierDataScaled(f.barrierType, barriers, f.rebate * Notional, tradeBarriers);
332 FxBarrierOption fxBarrierOptionNotional(env, optionData, barrierDataScaled, Date(), "", "EUR", Notional, // foreign
333 "JPY", Notional * f.k); // domestic
334
335 Real expectedNPV = f.result;
336
337 // Build and price
338 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
339 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
340 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
341 engineData->model("FxOption") = "GarmanKohlhagen";
342 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
343
344 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
345
346 fxBarrierOption.build(engineFactory);
347 fxBarrierOptionNotional.build(engineFactory);
348
349 Real npv = (position == "Long" ? 1 : -1) * fxBarrierOption.instrument()->NPV();
350
351 BOOST_TEST_MESSAGE("NPV Currency " << fxBarrierOption.npvCurrency());
352 BOOST_TEST_MESSAGE("FX Barrier Option NPV = " << npv);
353
354 // Check NPV matches expected values.
355 QL_REQUIRE(fxBarrierOption.npvCurrency() == "JPY", "unexpected NPV currency ");
356
357 BOOST_CHECK_CLOSE(npv, expectedNPV, 0.2);
358 BOOST_CHECK_CLOSE(fxBarrierOption.instrument()->NPV() * 1000000,
359 fxBarrierOptionNotional.instrument()->NPV(), 0.2);
360 Settings::instance().evaluationDate() = today; // reset
361 }
362 }
363}
364
365BOOST_AUTO_TEST_CASE(testFXBarrierOptionSymmetry) {
366 BOOST_TEST_MESSAGE("Testing FXBarrierOption Symmetry...");
367 //"Option pricing formulas, Second Edition", E.G. Haug, McGraw-Hill 2007 - page 168
368 // For single barrier options the symmetry between put and call options is:
369 // c_di(spot, strike, barrier, r, q, vol) = p_ui(strike, spot, strike*spot/barrier, q, r, vol);
370 // c_di(spot, strike, barrier, r, q, vol) = p_di(strike, spot, strike*spot/barrier, q, r, vol);
371
372 BarrierOptionData fxb[] = {// barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
373 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 9.0246},
374 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 7.7627},
375 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 4.0109},
376 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 2.0576},
377 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 13.8333},
378 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 7.8494},
379 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 3.9795},
380 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 9.0093},
381 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 5.1370},
382 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 2.8517},
383 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 14.8816},
384 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 9.2045},
385 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 5.3043}};
386
387 for (auto& f : fxb) {
388 // build market
389 QuantLib::ext::shared_ptr<Market> marketCall = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
390 QuantLib::ext::shared_ptr<Market> marketPut = QuantLib::ext::make_shared<TestMarket>(f.k, f.r, f.q, f.v);
391 Date today = Settings::instance().evaluationDate();
392 Settings::instance().evaluationDate() = marketCall->asofDate();
393
394 // build FXBarrierOptions - expiry in 6 months
395 OptionData optionCallData("Long", "Call", "European", true, vector<string>(1, "20160801"));
396 OptionData optionPutData("Long", "Put", "European", true, vector<string>(1, "20160801"));
397 vector<Real> barriersCall = {f.barrier};
398 vector<Real> barriersPut = {f.s * f.k / f.barrier};
399 vector<TradeBarrier> tradeBarriers_call, tradeBarriers_put;
400 tradeBarriers_call.push_back(TradeBarrier(f.barrier, ""));
401 tradeBarriers_put.push_back(TradeBarrier(f.s * f.k / f.barrier, ""));
402 BarrierData barrierCallData("DownAndIn", barriersCall, 0.0, tradeBarriers_call);
403 BarrierData barrierPutData("UpAndIn", barriersPut, 0.0, tradeBarriers_put);
404 Envelope env("CP1");
405
406 FxBarrierOption fxCallOption(env, optionCallData, barrierCallData, Date(), "", "EUR", 1, // foreign
407 "JPY", f.k); // domestic
408 FxBarrierOption fxPutOption(env, optionPutData, barrierPutData, Date(), "", "EUR", 1, // foreign
409 "JPY", f.s); // domestic
410
411 // Build and price
412 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
413 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
414 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
415 engineData->model("FxOption") = "GarmanKohlhagen";
416 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
417
418 QuantLib::ext::shared_ptr<EngineFactory> engineFactoryCall = QuantLib::ext::make_shared<EngineFactory>(engineData, marketCall);
419 QuantLib::ext::shared_ptr<EngineFactory> engineFactoryPut = QuantLib::ext::make_shared<EngineFactory>(engineData, marketPut);
420
421 fxCallOption.build(engineFactoryCall);
422 fxPutOption.build(engineFactoryPut);
423
424 Real npvCall = fxCallOption.instrument()->NPV();
425 Real npvPut = fxPutOption.instrument()->NPV();
426
427 BOOST_TEST_MESSAGE("NPV Currency " << fxCallOption.npvCurrency());
428 BOOST_TEST_MESSAGE("FX Barrier Option, NPV Call " << npvCall);
429 BOOST_TEST_MESSAGE("FX Barrier Option, NPV Put " << npvPut);
430 // Check NPV matches expected values.
431 BOOST_CHECK_CLOSE(npvCall, npvPut, 0.01);
432
433 Settings::instance().evaluationDate() = today; // reset
434 }
435}
436
437BOOST_AUTO_TEST_CASE(testFXBarrierOptionParity) {
438 BOOST_TEST_MESSAGE("Testing FXBarrierOption Parity...");
439
440 // An "In" option and an "Out" option with the same strikes and expiries should have the same combined price as a
441 // vanilla Call option
442 BarrierOptionData fxb[] = {
443 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
444 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
445 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
446 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
447 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
448 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
449 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
450 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
451 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
452 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
453 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
454 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
455 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
456 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
457 };
458
459 for (auto& f : fxb) {
460 // build market
461 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
462 Date today = Settings::instance().evaluationDate();
463 Settings::instance().evaluationDate() = market->asofDate();
464
465 // build FXBarrierOption - expiry in 6 months
466 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
467
468 vector<Real> barriers = {f.barrier};
469 vector<TradeBarrier> tradeBarriers;
470 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
471 BarrierData downInData("DownAndIn", barriers, 0.0, tradeBarriers);
472 BarrierData upInData("UpAndIn", barriers, 0.0, tradeBarriers);
473 BarrierData downOutData("DownAndOut", barriers, 0.0, tradeBarriers);
474 BarrierData upOutData("UpAndOut", barriers, 0.0, tradeBarriers);
475
476 Envelope env("CP1");
477
478 FxOption fxOption(env, optionData, "EUR", 1, // foreign
479 "JPY", f.k);
480
481 FxBarrierOption downInOption(env, optionData, downInData, Date(), "", "EUR", 1, // foreign
482 "JPY", f.k); // domestic
483 FxBarrierOption upInOption(env, optionData, upInData, Date(), "", "EUR", 1, // foreign
484 "JPY", f.k); // domestic
485 FxBarrierOption downOutOption(env, optionData, downOutData, Date(), "", "EUR", 1, // foreign
486 "JPY", f.k); // domestic
487 FxBarrierOption upOutOption(env, optionData, upOutData, Date(), "", "EUR", 1, // foreign
488 "JPY", f.k); // domestic
489
490 // Build and price
491 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
492 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
493 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
494 engineData->model("FxOption") = "GarmanKohlhagen";
495 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
496
497 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
498
499 fxOption.build(engineFactory);
500 downInOption.build(engineFactory);
501 upInOption.build(engineFactory);
502 downOutOption.build(engineFactory);
503 upOutOption.build(engineFactory);
504
505 Real npv = fxOption.instrument()->NPV();
506
507 // Check NPV matches expected values.
508 BOOST_CHECK_CLOSE(npv, downInOption.instrument()->NPV() + downOutOption.instrument()->NPV(), 0.01);
509 BOOST_CHECK_CLOSE(npv, upInOption.instrument()->NPV() + upOutOption.instrument()->NPV(), 0.01);
510
511 Settings::instance().evaluationDate() = today; // reset
512 }
513}
514
515BOOST_AUTO_TEST_CASE(testFXBarrierOptionTouched) {
516 BOOST_TEST_MESSAGE("Testing FXBarrierOption when barrier already touched...");
517
518 // An "In" option is equivalent to an fxOption once the barrier has been touched
519 // An "Out" option has zero value once the barrier has been touched and the rebate paid
520 BarrierOptionData fxb[] = {// barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
521 {"DownAndIn", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
522 {"DownAndIn", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
523 {"DownAndIn", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
524 {"DownAndIn", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
525 {"DownAndIn", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
526 {"DownAndIn", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
527 {"UpAndIn", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
528 {"UpAndIn", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
529 {"UpAndIn", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
530
531 {"DownAndIn", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
532 {"DownAndIn", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
533 {"DownAndIn", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
534 {"DownAndIn", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
535 {"DownAndIn", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
536 {"DownAndIn", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
537 {"UpAndIn", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
538 {"UpAndIn", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
539 {"UpAndIn", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
540
541 {"DownAndOut", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
542 {"DownAndOut", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
543 {"DownAndOut", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
544 {"DownAndOut", 100.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
545 {"DownAndOut", 100.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
546 {"DownAndOut", 100.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
547 {"UpAndOut", 95.0, 3.0, "Call", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
548 {"UpAndOut", 95.0, 3.0, "Call", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
549 {"UpAndOut", 95.0, 3.0, "Call", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
550
551 {"DownAndOut", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
552 {"DownAndOut", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
553 {"DownAndOut", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
554 {"DownAndOut", 100.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
555 {"DownAndOut", 100.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
556 {"DownAndOut", 100.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
557 {"UpAndOut", 95.0, 3.0, "Put", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
558 {"UpAndOut", 95.0, 3.0, "Put", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
559 {"UpAndOut", 95.0, 3.0, "Put", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0}};
560
561 for (auto& f : fxb) {
562 // build market
563 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
564 Date today = Settings::instance().evaluationDate();
565 Settings::instance().evaluationDate() = market->asofDate();
566
567 // build FXBarrierOption - expiry in 6 months
568 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, "20160801"));
569
570 vector<Real> barriers = {f.barrier};
571 vector<TradeBarrier> tradeBarriers;
572 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
573 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
574
575 Envelope env("CP1");
576
577 FxBarrierOption fxBarrierOption(env, optionData, barrierData, Date(1, Month::February, 2016),
578 "TARGET", "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
579 FxOption fxOption(env, optionData, "EUR", 1, // foreign
580 "JPY", f.k); // domestic
581
582 FxBarrierOption fxBarrierOptionInverted(env, optionData, barrierData, Date(1, Month::February, 2016),
583 "TARGET", "EUR", 1, // foreign
584 "JPY", f.k, "FX-Reuters-JPY-EUR"); // domestic
585
586 // Build and price
587 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
588 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
589 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
590 engineData->model("FxOption") = "GarmanKohlhagen";
591 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
592
593 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
594
595 fxOption.build(engineFactory);
596 fxBarrierOption.build(engineFactory);
597 fxBarrierOptionInverted.build(engineFactory);
598
599 // Check NPV matches expected values.
600 if (f.barrierType == "DownAndIn" || f.barrierType == "UpAndIn") {
601 BOOST_CHECK_CLOSE(fxBarrierOption.instrument()->NPV(), fxOption.instrument()->NPV(), 0.01);
602 BOOST_CHECK_CLOSE(fxBarrierOptionInverted.instrument()->NPV(), fxOption.instrument()->NPV(), 0.01);
603 } else {
604 BOOST_CHECK_CLOSE(fxBarrierOption.instrument()->NPV(), 0.0, 0.01);
605 BOOST_CHECK_CLOSE(fxBarrierOptionInverted.instrument()->NPV(), 0.0, 0.01);
606 }
607
608 Settings::instance().evaluationDate() = today; // reset
609 IndexManager::instance().clearHistories(); // reset
610 }
611}
612
613struct DigitalBarrierOptionData {
614 string barrierType;
615 Real barrier;
616 Real cash; // cash payoff for cash-or-nothing
617 string optionType;
618 Real k;
619 Real s; // spot
620 Rate q; // dividend
621 Rate r; // risk-free rate
622 Time t; // time to maturity
623 Real v; // volatility
624 Real result; // expected result
625};
626
627BOOST_AUTO_TEST_CASE(testFXDigitalBarrierOptionPrice) {
628 BOOST_TEST_MESSAGE("Testing FXDigitalBarrierOption Price...");
629
630 // Testing option values when barrier untouched
631 // Values from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 180
632 DigitalBarrierOptionData fxb[] = {
633 // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value
634 {"DownAndIn", 100.00, 15.00, "Call", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.9289},
635 {"DownAndIn", 100.00, 15.00, "Call", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 6.2150},
636 {"UpAndIn", 100.00, 15.00, "Call", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 5.8926}, // 5.3710 in Haug's book
637 {"UpAndIn", 100.00, 15.00, "Call", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 7.4519},
638 {"DownAndIn", 100.00, 15.00, "Put", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.4314},
639 {"DownAndIn", 100.00, 15.00, "Put", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 3.1454},
640 {"UpAndIn", 100.00, 15.00, "Put", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 5.3297},
641 {"UpAndIn", 100.00, 15.00, "Put", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.7704},
642 {"DownAndOut", 100.00, 15.00, "Call", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.8758},
643 {"DownAndOut", 100.00, 15.00, "Call", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 4.9081},
644 {"UpAndOut", 100.00, 15.00, "Call", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0000},
645 {"UpAndOut", 100.00, 15.00, "Call", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0407},
646 {"DownAndOut", 100.00, 15.00, "Put", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0323},
647 {"DownAndOut", 100.00, 15.00, "Put", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0000},
648 {"UpAndOut", 100.00, 15.00, "Put", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.0461},
649 {"UpAndOut", 100.00, 15.00, "Put", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 3.0054}};
650
651 for (auto& f : fxb) {
652 // build market
653 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
654 Date today = market->asofDate();
655 Settings::instance().evaluationDate() = market->asofDate();
656
657 // build FXOption - expiry in 6 months
658 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, "20160801"));
659
660 vector<Real> barriers = {f.barrier};
661 vector<TradeBarrier> tradeBarriers;
662 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
663 BarrierData barrierData(f.barrierType, barriers, 0, tradeBarriers);
664
665 Envelope env("CP1");
666 FxDigitalBarrierOption barrierOption(env, optionData, barrierData, f.k, f.cash, "EUR", // foreign
667 "JPY"); // domestic
668
669 Real expectedNPV = f.result / f.cash;
670
671 // Build and price
672 map<string, string> engineParamMap;
673 engineParamMap["Scheme"] = "Douglas";
674 engineParamMap["TimeGridPerYear"] = "800";
675 engineParamMap["XGrid"] = "400";
676 engineParamMap["DampingSteps"] = "100";
677
678 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
679 engineData->model("FxDigitalBarrierOption") = "GarmanKohlhagen";
680 engineData->engine("FxDigitalBarrierOption") = "FdBlackScholesBarrierEngine";
681 engineData->engineParameters("FxDigitalBarrierOption") = engineParamMap;
682 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
683 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
684 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
685 barrierOption.build(engineFactory);
686
687 Real npv = barrierOption.instrument()->NPV() / f.cash;
688
689 BOOST_TEST_MESSAGE("NPV Currency " << barrierOption.npvCurrency());
690
691 // Check NPV matches expected values.
692 // TODO: Implement analytical formula to improve accuracy
693 BOOST_CHECK_SMALL(npv - expectedNPV, 1e-3);
694 Settings::instance().evaluationDate() = today; // reset
695 }
696}
697
698BOOST_AUTO_TEST_CASE(testFXDigitalBarrierOptionParity) {
699 BOOST_TEST_MESSAGE("Testing FXDigitalBarrierOption Price...");
700 // An "In" option and an "Out" option with the same strikes and expiries should have the same combined price as a
701 // vanilla Call option
702
703 DigitalBarrierOptionData fxb[] = {
704 // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value
705 {"", 100.00, 15.00, "Call", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
706 {"", 100.00, 15.00, "Call", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
707 {"", 100.00, 15.00, "Call", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
708 {"", 100.00, 15.00, "Call", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
709 {"", 100.00, 15.00, "Put", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
710 {"", 100.00, 15.00, "Put", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
711 {"", 100.00, 15.00, "Put", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
712 {"", 100.00, 15.00, "Put", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
713 {"", 100.00, 15.00, "Call", 102.00, 95.00, -0.14, 0.10, 0.5, 0.20, 0.0},
714 {"", 100.00, 15.00, "Call", 102.00, 95.00, 0.03, 0.10, 0.5, 0.20, 0.0},
715 {"", 100.00, 15.00, "Put", 102.00, 98.00, 0.00, 0.10, 0.5, 0.20, 0.0},
716 {"", 100.00, 15.00, "Put", 102.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0},
717 {"", 100.00, 15.00, "Call", 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0},
718 {"", 100.00, 15.00, "Call", 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0},
719 {"", 100.00, 15.00, "Put", 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0},
720 {"", 100.00, 15.00, "Put", 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0}};
721
722 vector<string> payoutCcys = {"EUR", "JPY"};
723 for (auto& f : fxb) {
724 // build market
725 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
726 Date today = market->asofDate();
727 Settings::instance().evaluationDate() = market->asofDate();
728 for (auto& payoutCcy : payoutCcys) {
729 // build FXOption - expiry in 9 months
730 // Date exDate = today + Integer(f.t*360+0.5);
731 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, "20160801"));
732
733 vector<Real> barriers = {f.barrier};
734 vector<TradeBarrier> tradeBarriers;
735 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
736
737 BarrierData downInData("DownAndIn", barriers, 0.0, tradeBarriers);
738 BarrierData upInData("UpAndIn", barriers, 0.0, tradeBarriers);
739 BarrierData downOutData("DownAndOut", barriers, 0.0, tradeBarriers);
740 BarrierData upOutData("UpAndOut", barriers, 0.0, tradeBarriers);
741 BarrierData barrierData(f.barrierType, barriers, 0, tradeBarriers);
742
743 Envelope env("CP1");
744 FxDigitalOption fxOption(env, optionData, f.k, payoutCcy, f.cash, "EUR", // foreign
745 "JPY"); // domestic
746
747 FxDigitalBarrierOption downInOption(env, optionData, downInData, f.k, f.cash, "EUR", // foreign
748 "JPY", "", "", "", payoutCcy); // domestic
749 FxDigitalBarrierOption upInOption(env, optionData, upInData, f.k, f.cash, "EUR", // foreign
750 "JPY", "", "", "", payoutCcy); // domestic
751 FxDigitalBarrierOption downOutOption(env, optionData, downOutData, f.k, f.cash, "EUR", // foreign
752 "JPY", "", "", "", payoutCcy); // domestic
753 FxDigitalBarrierOption upOutOption(env, optionData, upOutData, f.k, f.cash, "EUR", // foreign
754 "JPY", "", "", "", payoutCcy); // domestic
755
756 // Build and price
757 map<string, string> engineParamMap;
758 engineParamMap["Scheme"] = "Douglas";
759 engineParamMap["TimeGridPerYear"] = "400";
760 engineParamMap["XGrid"] = "400";
761 engineParamMap["DampingSteps"] = "100";
762
763 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
764 engineData->model("FxDigitalBarrierOption") = "GarmanKohlhagen";
765 engineData->engine("FxDigitalBarrierOption") = "FdBlackScholesBarrierEngine";
766 engineData->engineParameters("FxDigitalBarrierOption") = engineParamMap;
767 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
768 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
769 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
770
771 fxOption.build(engineFactory);
772 downInOption.build(engineFactory);
773 upInOption.build(engineFactory);
774 downOutOption.build(engineFactory);
775 upOutOption.build(engineFactory);
776
777 Real npv = fxOption.instrument()->NPV();
778
779 BOOST_TEST_MESSAGE("NPV Currency " << fxOption.npvCurrency());
780
781 // Check NPV matches expected values.
782 // Check NPV matches expected values.
783 BOOST_CHECK_CLOSE(npv, downInOption.instrument()->NPV() + downOutOption.instrument()->NPV(), 0.1);
784 BOOST_CHECK_CLOSE(npv, upInOption.instrument()->NPV() + upOutOption.instrument()->NPV(), 0.1);
785 }
786 Settings::instance().evaluationDate() = today; // reset
787 }
788}
789
790BOOST_AUTO_TEST_CASE(testFXDigitalBarrierOptionTouched) {
791 BOOST_TEST_MESSAGE("Testing FXDigitalBarrierOption Price...");
792
793 DigitalBarrierOptionData fxb[] = {
794 // barrierType, barrier, cash, type, strike, spot, q, r, t, vol, value
795 {"", 100.00, 15.00, "Call", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
796 {"", 100.00, 15.00, "Call", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
797 {"", 100.00, 15.00, "Call", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
798 {"", 100.00, 15.00, "Call", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
799 {"", 100.00, 15.00, "Put", 102.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
800 {"", 100.00, 15.00, "Put", 98.00, 105.00, 0.00, 0.10, 0.5, 0.20, 0.0},
801 {"", 100.00, 15.00, "Put", 102.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
802 {"", 100.00, 15.00, "Put", 98.00, 95.00, 0.00, 0.10, 0.5, 0.20, 0.0},
803 {"", 100.00, 15.00, "Call", 102.00, 95.00, -0.14, 0.10, 0.5, 0.20, 0.0},
804 {"", 100.00, 15.00, "Call", 102.00, 95.00, 0.03, 0.10, 0.5, 0.20, 0.0},
805 {"", 100.00, 15.00, "Put", 102.00, 98.00, 0.00, 0.10, 0.5, 0.20, 0.0},
806 {"", 100.00, 15.00, "Put", 102.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0},
807 {"", 100.00, 15.00, "Call", 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0},
808 {"", 100.00, 15.00, "Call", 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0},
809 {"", 100.00, 15.00, "Put", 98.00, 99.00, 0.00, 0.10, 0.5, 0.20, 0.0},
810 {"", 100.00, 15.00, "Put", 98.00, 101.00, 0.00, 0.10, 0.5, 0.20, 0.0}};
811
812 vector<string> payoutCcys = {"EUR", "JPY"};
813 vector<string> fxIndices = {"FX-Reuters-EUR-JPY", "FX-Reuters-JPY-EUR"};
814 for (auto& f : fxb) {
815 // build market
816 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
817 Date today = market->asofDate();
818 Settings::instance().evaluationDate() = market->asofDate();
819 for (auto& payoutCcy : payoutCcys) {
820 for (auto& fxIndex : fxIndices) {
821 // build FXOption - expiry in 6 months
822 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, "20160801"));
823
824 vector<Real> barriers = {f.barrier};
825 vector<TradeBarrier> tradeBarriers;
826 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
827
828 BarrierData downInData("DownAndIn", barriers, 0.0, tradeBarriers);
829 BarrierData upInData("UpAndIn", barriers, 0.0, tradeBarriers);
830 BarrierData downOutData("DownAndOut", barriers, 0.0, tradeBarriers);
831 BarrierData upOutData("UpAndOut", barriers, 0.0, tradeBarriers);
832
833 Envelope env("CP1");
834 FxDigitalOption fxOption(env, optionData, f.k, payoutCcy, f.cash, "EUR", // foreign
835 "JPY"); // domestic
836 FxDigitalBarrierOption downInOption(env, optionData, downInData, f.k, f.cash, "EUR", // foreign
837 "JPY", "20160201", "TARGET", fxIndex, payoutCcy);
838 FxDigitalBarrierOption upInOption(env, optionData, upInData, f.k, f.cash, "EUR", // foreign
839 "JPY", "20160201", "TARGET", fxIndex, payoutCcy);
840 FxDigitalBarrierOption downOutOption(env, optionData, downOutData, f.k, f.cash, "EUR", // foreign
841 "JPY", "20160201", "TARGET", fxIndex, payoutCcy);
842 FxDigitalBarrierOption upOutOption(env, optionData, upOutData, f.k, f.cash, "EUR", // foreign
843 "JPY", "20160201", "TARGET", fxIndex, payoutCcy);
844
845 // Build and price
846 map<string, string> engineParamMap;
847 engineParamMap["Scheme"] = "Douglas";
848 engineParamMap["TimeGridPerYear"] = "400";
849 engineParamMap["XGrid"] = "400";
850 engineParamMap["DampingSteps"] = "100";
851
852 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
853 engineData->model("FxDigitalBarrierOption") = "GarmanKohlhagen";
854 engineData->engine("FxDigitalBarrierOption") = "FdBlackScholesBarrierEngine";
855 engineData->engineParameters("FxDigitalBarrierOption") = engineParamMap;
856 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
857 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
858 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
859
860 fxOption.build(engineFactory);
861 downInOption.build(engineFactory);
862 upInOption.build(engineFactory);
863 downOutOption.build(engineFactory);
864 upOutOption.build(engineFactory);
865
866 Real npv = fxOption.instrument()->NPV();
867
868 BOOST_TEST_MESSAGE("NPV Currency " << fxOption.npvCurrency());
869
870 // Check NPV matches expected values.
871 BOOST_CHECK_CLOSE(npv, downInOption.instrument()->NPV(), 0.01);
872 BOOST_CHECK_CLOSE(npv, upInOption.instrument()->NPV(), 0.01);
873 BOOST_CHECK_CLOSE(0.0, downOutOption.instrument()->NPV(), 0.01);
874 BOOST_CHECK_CLOSE(0.0, upOutOption.instrument()->NPV(), 0.01);
875 }
876 }
877 Settings::instance().evaluationDate() = today; // reset
878 IndexManager::instance().clearHistories(); // reset
879 }
880}
881
882struct FxTouchOptionData {
883 string barrierType;
884 Real barrier;
885 Real cash; // cash payoff
886 bool payoffAtExpiry;
887 string optionType;
888 bool payoffCurrencyDomestic;
889 Real s; // spot
890 Rate q; // foreign risk-free rate
891 Rate r; // domestic risk-free rate
892 Time t; // time to maturity
893 Real v; // volatility
894 Real result; // expected result
895};
896
897BOOST_AUTO_TEST_CASE(testFXTouchOptionPrice) {
898 BOOST_TEST_MESSAGE("Testing FXTouchOption Price...");
899
900 // The following results are from Table 4.22, pp 180 of
901 // "The Complete Guide to Option Pricing Formulas" (2nd Ed) by E. G. Haug
902 FxTouchOptionData fxd[] = {
903 // barrierType, barrier, cash, payAtExp, type, payDom, s, q, r, t, vol, value
904 {"DownAndIn", 100.0, 15.0, true, "Put", true, 105.0, 0.0, 0.1, 0.5, 0.2, 9.3604},
905 {"UpAndIn", 100.0, 15.0, true, "Call", true, 95.0, 0.0, 0.1, 0.5, 0.2, 11.2223},
906 {"DownAndOut", 100.0, 15.0, true, "Put", true, 105.0, 0.0, 0.1, 0.5, 0.2, 4.9081},
907 {"UpAndOut", 100.0, 15.0, true, "Call", true, 95.0, 0.0, 0.1, 0.5, 0.2, 3.0461},
908
909 // payoff at hit. The following are the corresponding test cases from Haug.
910 {"DownAndIn", 100.0, 15.0, false, "Put", true, 105.0, 0.0, 0.1, 0.5, 0.2, 9.3604},
911 {"UpAndIn", 100.0, 15.0, false, "Call", true, 95.0, 0.0, 0.1, 0.5, 0.2, 11.2223},
912
913 // check that if the option has already knocked in or out then the option prices appropriately.
914 {"DownAndIn", 100.0, 15.0, true, "Put", true, 95.0, 0.0, 0.1, 0.5, 0.2, 14.2684},
915 {"UpAndIn", 100.0, 15.0, true, "Call", true, 105.0, 0.0, 0.1, 0.5, 0.2, 14.2684},
916 {"DownAndOut", 100.0, 15.0, true, "Put", true, 95.0, 0.0, 0.1, 0.5, 0.2, 0.0},
917 {"UpAndOut", 100.0, 15.0, true, "Call", true, 105.0, 0.0, 0.1, 0.5, 0.2, 0.0},
918
919 // check consistent pricing in the limit of high barrier level
920 {"UpAndIn", 1000.0, 15.0, true, "Call", true, 100.0, 0.0, 0.1, 0.5, 0.2, 0.0},
921 {"UpAndOut", 1000.0, 15.0, true, "Call", true, 100.0, 0.0, 0.1, 0.5, 0.2, 14.2684},
922 };
923
924 // Set engineData
925 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
926 engineData->model("FxTouchOption") = "GarmanKohlhagen";
927 engineData->engine("FxTouchOption") = "AnalyticDigitalAmericanEngine";
928 engineData->model("Swap") = "DiscountedCashflows";
929 engineData->engine("Swap") = "DiscountingSwapEngine";
930
931 for (auto& f : fxd) {
932 // build market
933 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
934
935 Date today = Settings::instance().evaluationDate();
936 Settings::instance().evaluationDate() = market->asofDate();
937
938 // build FxTouchOption expiring in 6 months
939 vector<Real> barriers = {f.barrier};
940 vector<TradeBarrier> tradeBarriers;
941 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
942 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
943 OptionData optionData("Long", f.optionType, "American", f.payoffAtExpiry, vector<string>(1, "20160801"));
944 Envelope env("CP1");
945 FxTouchOption fxTouchOption(env, optionData, barrierData, "EUR", "JPY",
946 f.payoffCurrencyDomestic ? "JPY" : "EUR", f.cash);
947
948 Real expectedNPV = f.result;
949
950 // Build and price
951 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
952
953 fxTouchOption.build(engineFactory);
954
955 Real npv = fxTouchOption.instrument()->NPV();
956 string ccy = fxTouchOption.npvCurrency();
957
958 BOOST_TEST_MESSAGE("FX Touch Option, NPV Currency " << ccy);
959 BOOST_TEST_MESSAGE("NPV = " << npv);
960 BOOST_TEST_MESSAGE("Expected NPV = " << expectedNPV);
961
962 BOOST_CHECK_SMALL(npv - expectedNPV, 0.01);
963 Settings::instance().evaluationDate() = today; // reset
964 }
965}
966
967BOOST_AUTO_TEST_CASE(testFXTouchOptionParity) {
968 BOOST_TEST_MESSAGE("Testing FXTouchOption Parity...");
969 // An "In" option and an "Out" option with the same strikes and expiries should have the same combined price as a
970 // cashflow
971
972 FxTouchOptionData fxb[] = {
973 // barrierType, barrier, cash, payAtExp, type, payDom, s, q, r, t, vol, value
974 {"", 0.0, 1e6, true, "", true, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
975 {"", 95.0, 1e6, true, "", true, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
976 {"", 100.0, 1e6, true, "", true, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
977 {"", 105.0, 1e6, true, "", true, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
978 {"", 999.0, 1e6, true, "", true, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0}};
979
980 for (auto& f : fxb) {
981 // build market
982 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
983 Date today = Settings::instance().evaluationDate();
984 Settings::instance().evaluationDate() = market->asofDate();
985
986 // build FXBarrierOption - expiry in 6 months
987 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
988
989 vector<Real> barriers = {f.barrier};
990 vector<TradeBarrier> tradeBarriers;
991 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
992
993 BarrierData downInData("DownAndIn", barriers, 0.0, tradeBarriers);
994 BarrierData upInData("UpAndIn", barriers, 0.0, tradeBarriers);
995 BarrierData downOutData("DownAndOut", barriers, 0.0, tradeBarriers);
996 BarrierData upOutData("UpAndOut", barriers, 0.0, tradeBarriers);
997
998 Envelope env("CP1");
999
1000 vector<double> amounts = {f.cash};
1001 vector<string> dates = {"2016-08-01"};
1002
1003 LegData legData(QuantLib::ext::make_shared<CashflowData>(amounts, dates), true,
1004 f.payoffCurrencyDomestic ? "JPY" : "EUR");
1005 legData.isPayer() = false;
1006 ore::data::Swap swap(env, {legData});
1007
1008 FxTouchOption downInOption(env, optionData, downInData, "EUR", "JPY", f.payoffCurrencyDomestic ? "JPY" : "EUR",
1009 f.cash);
1010 FxTouchOption upInOption(env, optionData, upInData, "EUR", "JPY", f.payoffCurrencyDomestic ? "JPY" : "EUR",
1011 f.cash);
1012 FxTouchOption downOutOption(env, optionData, downOutData, "EUR", "JPY",
1013 f.payoffCurrencyDomestic ? "JPY" : "EUR", f.cash);
1014 FxTouchOption upOutOption(env, optionData, upOutData, "EUR", "JPY", f.payoffCurrencyDomestic ? "JPY" : "EUR",
1015 f.cash);
1016
1017 // Build and price
1018 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1019 engineData->model("FxTouchOption") = "GarmanKohlhagen";
1020 engineData->engine("FxTouchOption") = "AnalyticDigitalAmericanEngine";
1021 engineData->model("Swap") = "DiscountedCashflows";
1022 engineData->engine("Swap") = "DiscountingSwapEngine";
1023
1024 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1025
1026 swap.build(engineFactory);
1027 downInOption.build(engineFactory);
1028 upInOption.build(engineFactory);
1029 downOutOption.build(engineFactory);
1030 upOutOption.build(engineFactory);
1031
1032 Real npv = swap.instrument()->NPV();
1033
1034 // Check NPV matches expected values.
1035 BOOST_CHECK_CLOSE(npv, downInOption.instrument()->NPV() + downOutOption.instrument()->NPV(), 0.01);
1036 BOOST_CHECK_CLOSE(npv, upInOption.instrument()->NPV() + upOutOption.instrument()->NPV(), 0.01);
1037
1038 Settings::instance().evaluationDate() = today; // reset
1039 }
1040}
1041
1042BOOST_AUTO_TEST_CASE(testFXTouchOptionTouched) {
1043 BOOST_TEST_MESSAGE("Testing FXTouchOption when barrier already touched...");
1044
1045 struct FxTouchOptionTouchedData {
1046 string barrierType;
1047 Real barrier;
1048 Real cash;
1049 Real s; // spot
1050 Real s_1; // spot at t-1
1051 Real s_2; // spot at t-2
1052 Rate q; // dividend
1053 Rate r; // risk-free rate
1054 Real t; // time to maturity
1055 Volatility v; // volatility
1056 Real result; // expected result
1057 };
1058
1059 // An "In" option is equivalent to a cashflow once the barrier has been touched
1060 // An "Out" option has zero value once the barrier has been touched
1061 FxTouchOptionTouchedData fxt[] = {
1062 // barrierType, barrier, cash, s, s_1, s_2, q, r, t, v, result
1063 {"DownAndIn", 80.0, 1e6, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1064 {"DownAndIn", 80.0, 1e6, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1065 {"DownAndIn", 80.0, 1e6, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1066 {"DownAndIn", 80.0, 1e6, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1067 {"DownAndIn", 80.0, 1e6, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1068 {"DownAndIn", 80.0, 1e6, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1069
1070 {"UpAndIn", 120.0, 1e6, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1071 {"UpAndIn", 120.0, 1e6, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1072 {"UpAndIn", 120.0, 1e6, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1073 {"UpAndIn", 120.0, 1e6, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1074 {"UpAndIn", 120.0, 1e6, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1075 {"UpAndIn", 120.0, 1e6, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1076
1077 {"DownAndOut", 80.0, 1e6, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1078 {"DownAndOut", 80.0, 1e6, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1079 {"DownAndOut", 80.0, 1e6, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1080 {"DownAndOut", 80.0, 1e6, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1081 {"DownAndOut", 80.0, 1e6, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1082 {"DownAndOut", 80.0, 1e6, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1083
1084 {"UpAndOut", 120.0, 1e6, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1085 {"UpAndOut", 120.0, 1e6, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1086 {"UpAndOut", 120.0, 1e6, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1087 {"UpAndOut", 120.0, 1e6, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1088 {"UpAndOut", 120.0, 1e6, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1089 {"UpAndOut", 120.0, 1e6, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0}};
1090
1091 vector<string> payoutCcys = {"EUR", "JPY"};
1092 vector<string> fxIndices = {"FX-Reuters-EUR-JPY", "FX-Reuters-JPY-EUR"};
1093 for (auto& f : fxt) {
1094 // build market
1095 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
1096 Date today = Settings::instance().evaluationDate();
1097 Settings::instance().evaluationDate() = market->asofDate();
1098 TimeSeries<Real> pastFixings;
1099 pastFixings[market->asofDate() - 1 * Days] = f.s_1;
1100 pastFixings[market->asofDate() - 2 * Days] = f.s_2;
1101 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
1102 TimeSeries<Real> pastFixingsInverted;
1103 pastFixingsInverted[market->asofDate() - 1 * Days] = 1 / pastFixings[market->asofDate() - 1 * Days];
1104 pastFixingsInverted[market->asofDate() - 2 * Days] = 1 / pastFixings[market->asofDate() - 2 * Days];
1105 IndexManager::instance().setHistory("Reuters JPY/EUR", pastFixingsInverted);
1106 for (auto& payoutCcy : payoutCcys) {
1107 for (auto& fxIndex : fxIndices) {
1108 // build FXBarrierOption - expiry in 6 months
1109 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1110
1111 vector<Real> barriers = {f.barrier};
1112 vector<TradeBarrier> tradeBarriers;
1113 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
1114 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
1115
1116 Envelope env("CP1");
1117
1118 vector<double> amounts = {f.cash};
1119 vector<string> dates = {"2016-08-01"};
1120
1121 LegData legData(QuantLib::ext::make_shared<CashflowData>(amounts, dates), true, payoutCcy);
1122 legData.isPayer() = false;
1123 ore::data::Swap swap(env, {legData});
1124
1125 FxTouchOption touchOption(env, optionData, barrierData, "EUR", "JPY", payoutCcy, f.cash, "20160201",
1126 "TARGET", fxIndex);
1127
1128 // Build and price
1129 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1130 engineData->model("FxTouchOption") = "GarmanKohlhagen";
1131 engineData->engine("FxTouchOption") = "AnalyticDigitalAmericanEngine";
1132 engineData->model("Swap") = "DiscountedCashflows";
1133 engineData->engine("Swap") = "DiscountingSwapEngine";
1134
1135 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1136
1137 touchOption.build(engineFactory);
1138 swap.build(engineFactory);
1139
1140 // Check NPV matches expected values.
1141 if (f.barrierType == "DownAndIn" || f.barrierType == "UpAndIn")
1142 BOOST_CHECK_CLOSE(touchOption.instrument()->NPV(), swap.instrument()->NPV(), 0.01);
1143 else
1144 BOOST_CHECK_CLOSE(touchOption.instrument()->NPV(), 0.0, 0.01);
1145 }
1146 }
1147 Settings::instance().evaluationDate() = today; // reset
1148 IndexManager::instance().clearHistories(); // reset
1149 }
1150}
1151
1152struct DoubleBarrierOptionData {
1153 string barrierType;
1154 Real barrierLow;
1155 Real barrierHigh;
1156 Real rebate;
1157 string optionType; // option type
1158 Real k; // strike
1159 Real s; // spot
1160 Rate q; // dividend
1161 Rate r; // risk-free rate
1162 Real t; // time to maturity
1163 Volatility v; // volatility
1164 Real result; // expected result
1165};
1166
1167struct DoubleTouchOptionData {
1168 string barrierType;
1169 Real barrierLow;
1170 Real barrierHigh;
1171 Real cash;
1172 Real s; // spot
1173 Rate q; // dividend
1174 Rate r; // risk-free rate
1175 Real t; // time to maturity
1176 Volatility v; // volatility
1177 Real result; // expected result
1178};
1179
1180namespace {
1181// Testing option values when barrier untouched
1182// Values from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 158
1183DoubleBarrierOptionData fxdb[] = {
1184 // barrierType, barrierLow, barrierHigh, rebate, type, strk, s, q, r, t, v, result
1185 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3515},
1186 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.25, 6.1644},
1187 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.35, 7.0373},
1188 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.15, 6.9853},
1189 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.25, 7.9336},
1190 {"KnockOut", 50.0, 150.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.35, 6.5088},
1191
1192 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3505},
1193 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.25, 5.8500},
1194 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.35, 5.7726},
1195 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.15, 6.8082},
1196 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.25, 6.3383},
1197 {"KnockOut", 60.0, 140.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.35, 4.3841},
1198
1199 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.15, 4.3139},
1200 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.25, 4.8293},
1201 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.35, 3.7765},
1202 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.15, 5.9697},
1203 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.25, 4.0004},
1204 {"KnockOut", 70.0, 130.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.35, 2.2563},
1205
1206 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.15, 3.7516},
1207 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.25, 2.6387},
1208 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.35, 1.4903},
1209 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.15, 3.5805},
1210 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.25, 1.5098},
1211 {"KnockOut", 80.0, 120.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.5635},
1212
1213 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.15, 1.2055},
1214 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.25, 0.3098},
1215 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.25, 0.35, 0.0477},
1216 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.15, 0.5537},
1217 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.25, 0.0441},
1218 // Haug's result is 0.0011. Added 1 dp to pass closeness test
1219 {"KnockOut", 90.0, 110.0, 0.0, "Call", 100, 100.0, 0.0, 0.1, 0.50, 0.35, 0.00109}};
1220
1221// Testing option values when barrier untouched
1222// Values from "Option pricing formulas", E.G. Haug, McGraw-Hill 1998 - pag 181 & 182
1223DoubleTouchOptionData fxdt[] = {
1224 // barrierType, barrierLow, barrierHigh, cash, s, q, r, t, v, result
1225 {"KnockOut", 80.0, 120.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.1, 9.8716},
1226 {"KnockOut", 80.0, 120.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.2, 8.9307},
1227 {"KnockOut", 80.0, 120.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.3, 6.3272},
1228 {"KnockOut", 80.0, 120.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.5, 1.9094},
1229
1230 {"KnockOut", 85.0, 115.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.1, 9.7961},
1231 {"KnockOut", 85.0, 115.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.2, 7.2300},
1232 {"KnockOut", 85.0, 115.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.3, 3.7100},
1233 {"KnockOut", 85.0, 115.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.5, 0.4271},
1234
1235 {"KnockOut", 90.0, 110.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.1, 8.9054},
1236 {"KnockOut", 90.0, 110.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.2, 3.6752},
1237 {"KnockOut", 90.0, 110.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.3, 0.7960},
1238 {"KnockOut", 90.0, 110.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.5, 0.0059},
1239
1240 {"KnockOut", 95.0, 105.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.1, 3.6323},
1241 {"KnockOut", 95.0, 105.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.2, 0.0911},
1242 {"KnockOut", 95.0, 105.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.3, 0.0002},
1243 {"KnockOut", 95.0, 105.0, 10.0, 100.0, 0.02, 0.05, 0.25, 0.5, 0.0000}};
1244} // namespace
1245
1246BOOST_AUTO_TEST_CASE(testFXDoubleBarrierOptionPrice) {
1247 BOOST_TEST_MESSAGE("Testing FXDoubleBarrierOption Price...");
1248 for (auto& f : fxdb) {
1249 // build market
1250 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1251 Date today = Settings::instance().evaluationDate();
1252 Settings::instance().evaluationDate() = market->asofDate();
1253
1254 // build FXBarrierOption - expiry in 6 months
1255 Date exDate = today + Integer(f.t * 360 + 0.5);
1256 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, ore::data::to_string(exDate)));
1257 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1258 vector<TradeBarrier> tradeBarriers;
1259 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1260 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1261 BarrierData barrierData(f.barrierType, barriers, f.rebate, tradeBarriers);
1262 Envelope env("CP1");
1263 FxDoubleBarrierOption fxDoubleBarrierOption(env, optionData, barrierData, Date(), "",
1264 "EUR", 1, // foreign
1265 "JPY", f.k); // domestic
1266
1267 // we'll check that the results scale as expected
1268 // scaling the notional and the rebate by a million we should get npv_scaled = 1million * npv
1269 Real Notional = 1000000;
1270 BarrierData barrierDataScaled(f.barrierType, barriers, f.rebate * Notional, tradeBarriers);
1271 FxDoubleBarrierOption fxDoubleBarrierOptionNotional(env, optionData, barrierDataScaled, Date(), "",
1272 "EUR", Notional, // foreign
1273 "JPY", Notional * f.k); // domestic
1274
1275 Real expectedNPV = f.result;
1276
1277 // Build and price
1278 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1279 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
1280 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
1281 engineData->model("FxOption") = "GarmanKohlhagen";
1282 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1283
1284 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1285
1286 fxDoubleBarrierOption.build(engineFactory);
1287
1288 fxDoubleBarrierOptionNotional.build(engineFactory);
1289
1290 Real npv = fxDoubleBarrierOption.instrument()->NPV();
1291
1292 BOOST_TEST_MESSAGE("NPV Currency " << fxDoubleBarrierOption.npvCurrency());
1293 BOOST_TEST_MESSAGE("FX Barrier Option NPV = " << npv);
1294
1295 BOOST_CHECK_CLOSE(npv, expectedNPV, 0.2);
1296 BOOST_CHECK_CLOSE(fxDoubleBarrierOption.instrument()->NPV() * 1000000,
1297 fxDoubleBarrierOptionNotional.instrument()->NPV(), 0.2);
1298 Settings::instance().evaluationDate() = today; // reset
1299 }
1300}
1301
1302BOOST_AUTO_TEST_CASE(testFXDoubleBarrierOptionParity) {
1303 BOOST_TEST_MESSAGE("Testing FXDoubleBarrierOption Parity ...");
1304 for (auto& f : fxdb) {
1305 // build market
1306 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1307 Date today = Settings::instance().evaluationDate();
1308 Settings::instance().evaluationDate() = market->asofDate();
1309
1310 Date exDate = today + Integer(f.t * 360 + 0.5);
1311 OptionData optionData("Long", f.optionType, "European", true, vector<string>(1, ore::data::to_string(exDate)));
1312 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1313 vector<TradeBarrier> tradeBarriers;
1314 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1315 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1316 BarrierData barrierDataIn("KnockIn", barriers, f.rebate, tradeBarriers);
1317 BarrierData barrierDataOut("KnockOut", barriers, f.rebate, tradeBarriers);
1318 Envelope env("CP1");
1319 FxDoubleBarrierOption fxDoubleBarrierInOption(env, optionData, barrierDataIn, Date(), "",
1320 "EUR", 1, // foreign
1321 "JPY", f.k); // domestic
1322 FxDoubleBarrierOption fxDoubleBarrierOutOption(env, optionData, barrierDataOut, Date(), "",
1323 "EUR", 1, // foreign
1324 "JPY", f.k); // domestic
1325
1326 FxOption fxOption(env, optionData, "EUR", 1, // foreign
1327 "JPY", f.k); // domestic
1328
1329 // we'll check that the results scale as expected
1330 // scaling the notional and the rebate by a million we should get npv_scaled = 1million * npv
1331 Real Notional = 1000000;
1332 BarrierData barrierDataScaled(f.barrierType, barriers, f.rebate * Notional, tradeBarriers);
1333 FxDoubleBarrierOption fxDoubleBarrierOptionNotional(env, optionData, barrierDataScaled, Date(), "",
1334 "EUR", Notional, // foreign
1335 "JPY", Notional * f.k); // domestic
1336
1337 // Build and price
1338 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1339 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
1340 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
1341 engineData->model("FxOption") = "GarmanKohlhagen";
1342 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1343
1344 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1345
1346 fxDoubleBarrierInOption.build(engineFactory);
1347 fxDoubleBarrierOutOption.build(engineFactory);
1348 fxOption.build(engineFactory);
1349
1350 Real npv = fxDoubleBarrierInOption.instrument()->NPV();
1351
1352 BOOST_TEST_MESSAGE("NPV Currency " << fxDoubleBarrierInOption.npvCurrency());
1353 BOOST_TEST_MESSAGE("FX Barrier Option NPV = " << npv);
1354 BOOST_TEST_MESSAGE("FX Option NPV = " << fxOption.instrument()->NPV());
1355
1356 // Check NPV matches expected values.
1357 QL_REQUIRE(fxOption.npvCurrency() == "JPY", "unexpected NPV currency ");
1358
1359 BOOST_CHECK_CLOSE(fxDoubleBarrierInOption.instrument()->NPV() + fxDoubleBarrierOutOption.instrument()->NPV(),
1360 fxOption.instrument()->NPV(), 0.0000000002);
1361 Settings::instance().evaluationDate() = today; // reset
1362 }
1363}
1364
1365BOOST_AUTO_TEST_CASE(testFXDoubleBarrierOptionTouched) {
1366 BOOST_TEST_MESSAGE("Testing FXDoubleBarrierOption when barrier already touched...");
1367
1368 struct DoubleBarrierOptionTouchedData {
1369 string barrierType;
1370 Real barrierLow;
1371 Real barrierHigh;
1372 Real rebate;
1373 string type;
1374 Real k;
1375 Real s; // spot
1376 Real s_1; // spot at t-1
1377 Real s_2; // spot at t-2
1378 Rate q; // dividend
1379 Rate r; // risk-free rate
1380 Real t; // time to maturity
1381 Volatility v; // volatility
1382 Real result; // expected result
1383 };
1384
1385 // An "In" option is equivalent to a cashflow once the barrier has been touched
1386 // An "Out" option has zero value once the barrier has been touched
1387 DoubleBarrierOptionTouchedData fxdb[] = {
1388 // barrierType, barrierLow, barrierHigh, rebate, type, k, s, s_1, s_2, q, r, t, v,
1389 // result
1390 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1391 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1392 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1393 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1394 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1395 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1396
1397 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1398 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1399 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1400 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1401 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1402 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1403
1404 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1405 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1406 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1407 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1408 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1409 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1410
1411 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 80.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1412 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 80.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1413 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1414 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1415 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 70.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1416 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1417
1418 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1419 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1420 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1421 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1422 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1423 {"KnockIn", 80.0, 120.0, 3.0, "Call", 100.0, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1424
1425 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1426 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1427 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1428 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1429 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1430 {"KnockIn", 80.0, 120.0, 3.0, "Put", 100.0, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1431
1432 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1433 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1434 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1435 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1436 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1437 {"KnockOut", 80.0, 120.0, 3.0, "Call", 100.0, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1438
1439 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1440 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 120.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1441 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1442 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 100.0, 130.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1443 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 100.0, 130.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1444 {"KnockOut", 80.0, 120.0, 3.0, "Put", 100.0, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0}};
1445
1446 for (auto& f : fxdb) {
1447 // build market
1448 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
1449 Date today = Settings::instance().evaluationDate();
1450 Settings::instance().evaluationDate() = market->asofDate();
1451 TimeSeries<Real> pastFixings;
1452 pastFixings[market->asofDate() - 1 * Days] = f.s_1;
1453 pastFixings[market->asofDate() - 2 * Days] = f.s_2;
1454 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
1455 TimeSeries<Real> pastFixingsInverted;
1456 pastFixingsInverted[market->asofDate() - 1 * Days] = 1 / pastFixings[market->asofDate() - 1 * Days];
1457 pastFixingsInverted[market->asofDate() - 2 * Days] = 1 / pastFixings[market->asofDate() - 2 * Days];
1458 IndexManager::instance().setHistory("Reuters JPY/EUR", pastFixingsInverted);
1459
1460 // build FXBarrierOption - expiry in 6 months
1461 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1462
1463 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1464 vector<TradeBarrier> tradeBarriers;
1465 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1466 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1467 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
1468
1469 Envelope env("CP1");
1470
1471 FxDoubleBarrierOption doubleBarrierOption(env, optionData, barrierData, Date(1, Month::February, 2016), "TARGET",
1472 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
1473 FxDoubleBarrierOption doubleBarrierOptionInverted(env, optionData, barrierData, Date(1, Month::February, 2016), "TARGET",
1474 "EUR", 1, "JPY", f.k, "FX-Reuters-JPY-EUR");
1475 FxOption fxOption(env, optionData, "EUR", 1, // foreign
1476 "JPY", f.k); // domestic
1477
1478 // Build and price
1479 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1480 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
1481 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
1482 engineData->model("FxOption") = "GarmanKohlhagen";
1483 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1484
1485 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1486
1487 doubleBarrierOption.build(engineFactory);
1488 doubleBarrierOptionInverted.build(engineFactory);
1489 fxOption.build(engineFactory);
1490
1491 // Check NPV matches expected values.
1492 if (f.barrierType == "KnockIn") {
1493 BOOST_CHECK_CLOSE(doubleBarrierOption.instrument()->NPV(), fxOption.instrument()->NPV(), 0.01);
1494 BOOST_CHECK_CLOSE(doubleBarrierOptionInverted.instrument()->NPV(), fxOption.instrument()->NPV(), 0.01);
1495 } else {
1496 BOOST_CHECK_CLOSE(doubleBarrierOption.instrument()->NPV(), 0.0, 0.01);
1497 BOOST_CHECK_CLOSE(doubleBarrierOptionInverted.instrument()->NPV(), 0.0, 0.01);
1498 }
1499
1500 Settings::instance().evaluationDate() = today; // reset
1501 IndexManager::instance().clearHistories(); // reset
1502 }
1503}
1504
1505BOOST_AUTO_TEST_CASE(testFXDoubleTouchOptionPrice) {
1506 BOOST_TEST_MESSAGE("Testing FXDoubleTouchOption Price...");
1507
1508 // Set engineData
1509 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1510 engineData->model("FxDoubleTouchOption") = "GarmanKohlhagen";
1511 engineData->engine("FxDoubleTouchOption") = "AnalyticDoubleBarrierBinaryEngine";
1512 engineData->model("Swap") = "DiscountedCashflows";
1513 engineData->engine("Swap") = "DiscountingSwapEngine";
1514
1515 for (auto& f : fxdt) {
1516 // build market
1517 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1518
1519 Date today = Settings::instance().evaluationDate();
1520 Settings::instance().evaluationDate() = market->asofDate();
1521
1522 Date exDate = today + Integer(f.t * 360 + 0.5);
1523 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1524 vector<TradeBarrier> tradeBarriers;
1525 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1526 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1527 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
1528 OptionData optionData("Long", "Call", "European", true, vector<string>(1, ore::data::to_string(exDate)));
1529 Envelope env("CP1");
1530 FxDoubleTouchOption fxDoubleTouchOption(env, optionData, barrierData, "EUR", "JPY", "JPY", f.cash);
1531
1532 Real expectedNPV = f.result;
1533
1534 // Build and price
1535 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1536
1537 fxDoubleTouchOption.build(engineFactory);
1538
1539 Real npv = fxDoubleTouchOption.instrument()->NPV();
1540 string ccy = fxDoubleTouchOption.npvCurrency();
1541
1542 BOOST_TEST_MESSAGE("FX Double Touch Option, NPV Currency " << ccy);
1543 BOOST_TEST_MESSAGE("NPV = " << npv);
1544 BOOST_TEST_MESSAGE("Expected NPV = " << expectedNPV);
1545
1546 BOOST_CHECK_SMALL(npv - expectedNPV, 0.01);
1547 Settings::instance().evaluationDate() = today; // reset
1548 }
1549}
1550
1551BOOST_AUTO_TEST_CASE(testFXDoubleTouchOptionParity) {
1552 BOOST_TEST_MESSAGE("Testing FXDoubleTouchOption Parity...");
1553 // An "In" option and an "Out" option with the same strikes and expiries should have the same combined price as a
1554 // cashflow
1555
1556 for (auto& f : fxdt) {
1557 // build market
1558 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1559 Date today = Settings::instance().evaluationDate();
1560 Settings::instance().evaluationDate() = market->asofDate();
1561
1562 // build FXBarrierOption - expiry in 6 months
1563 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1564
1565 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1566 vector<TradeBarrier> tradeBarriers;
1567 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1568 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1569 BarrierData knonkOutData("KnockOut", barriers, 0.0, tradeBarriers);
1570 BarrierData knonkInData("KnockIn", barriers, 0.0, tradeBarriers);
1571
1572 Envelope env("CP1");
1573
1574 vector<double> amounts = {f.cash};
1575 vector<string> dates = {"2016-08-01"};
1576
1577 LegData legData(QuantLib::ext::make_shared<CashflowData>(amounts, dates), true, "JPY");
1578 legData.isPayer() = false;
1579 ore::data::Swap swap(env, {legData});
1580
1581 FxDoubleTouchOption knockOutOption(env, optionData, knonkOutData, "EUR", "JPY", "JPY", f.cash);
1582 FxDoubleTouchOption knockInOption(env, optionData, knonkInData, "EUR", "JPY", "JPY", f.cash);
1583
1584 // Build and price
1585 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1586 engineData->model("FxDoubleTouchOption") = "GarmanKohlhagen";
1587 engineData->engine("FxDoubleTouchOption") = "AnalyticDoubleBarrierBinaryEngine";
1588 engineData->model("Swap") = "DiscountedCashflows";
1589 engineData->engine("Swap") = "DiscountingSwapEngine";
1590
1591 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1592
1593 swap.build(engineFactory);
1594 knockOutOption.build(engineFactory);
1595 knockInOption.build(engineFactory);
1596
1597 Real npv = swap.instrument()->NPV();
1598
1599 // Check NPV matches expected values.
1600 BOOST_CHECK_CLOSE(npv, knockOutOption.instrument()->NPV() + knockInOption.instrument()->NPV(), 0.01);
1601
1602 Settings::instance().evaluationDate() = today; // reset
1603 }
1604}
1605
1606BOOST_AUTO_TEST_CASE(testFXDoubleTouchOptionTouched) {
1607 BOOST_TEST_MESSAGE("Testing FXDoubleTouchOption when barrier already touched...");
1608
1609 struct DoubleTouchOptionTouchedData {
1610 string barrierType;
1611 Real barrierLow;
1612 Real barrierHigh;
1613 Real cash;
1614 Real s; // spot
1615 Real s_1; // spot at t-1
1616 Real s_2; // spot at t-2
1617 Rate q; // dividend
1618 Rate r; // risk-free rate
1619 Real t; // time to maturity
1620 Volatility v; // volatility
1621 Real result; // expected result
1622 };
1623
1624 // An "In" option is equivalent to a cashflow once the barrier has been touched
1625 // An "Out" option has zero value once the barrier has been touched
1626 DoubleTouchOptionTouchedData fxdt[] = {
1627 // barrierType, barrierLow, barrierHigh, cash, s, s_1, s_2, q, r, t, v, result
1628 {"KnockIn", 80.0, 120.0, 1e6, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1629 {"KnockIn", 80.0, 120.0, 1e6, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1630 {"KnockOut", 80.0, 120.0, 1e6, 80.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1631 {"KnockOut", 80.0, 120.0, 1e6, 70.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1632
1633 {"KnockIn", 80.0, 120.0, 1e6, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1634 {"KnockIn", 80.0, 120.0, 1e6, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1635 {"KnockOut", 80.0, 120.0, 1e6, 120.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1636 {"KnockOut", 80.0, 120.0, 1e6, 130.0, 100.0, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1637
1638 {"KnockIn", 80.0, 120.0, 1e6, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1639 {"KnockIn", 80.0, 120.0, 1e6, 100.0, 70.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1640 {"KnockIn", 80.0, 120.0, 1e6, 70.0, 70.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1641 {"KnockOut", 80.0, 120.0, 1e6, 100.0, 100.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1642 {"KnockOut", 80.0, 120.0, 1e6, 100.0, 70.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1643 {"KnockOut", 80.0, 120.0, 1e6, 70.0, 70.0, 70.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1644
1645 {"KnockIn", 80.0, 120.0, 1e6, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1646 {"KnockIn", 80.0, 120.0, 1e6, 100.0, 120.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1647 {"KnockIn", 80.0, 120.0, 1e6, 120.0, 120.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1648 {"KnockOut", 80.0, 120.0, 1e6, 100.0, 100.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1649 {"KnockOut", 80.0, 120.0, 1e6, 100.0, 120.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1650 {"KnockOut", 80.0, 120.0, 1e6, 120.0, 120.0, 120.0, 0.04, 0.08, 0.50, 0.25, 0.0}};
1651
1652 vector<string> payoutCcys = {"EUR", "JPY"};
1653 vector<string> fxIndices = {"FX-Reuters-EUR-JPY", "FX-Reuters-JPY-EUR"};
1654 for (auto& f : fxdt) {
1655 // build market
1656 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
1657 Date today = Settings::instance().evaluationDate();
1658 Settings::instance().evaluationDate() = market->asofDate();
1659 TimeSeries<Real> pastFixings;
1660 pastFixings[market->asofDate() - 1 * Days] = f.s_1;
1661 pastFixings[market->asofDate() - 2 * Days] = f.s_2;
1662 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
1663 TimeSeries<Real> pastFixingsInverted;
1664 pastFixingsInverted[market->asofDate() - 1 * Days] = 1 / pastFixings[market->asofDate() - 1 * Days];
1665 pastFixingsInverted[market->asofDate() - 2 * Days] = 1 / pastFixings[market->asofDate() - 2 * Days];
1666 IndexManager::instance().setHistory("Reuters JPY/EUR", pastFixingsInverted);
1667
1668 for (auto& payoutCcy : payoutCcys) {
1669 for (auto& fxIndex : fxIndices) {
1670 // build FXBarrierOption - expiry in 6 months
1671 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1672
1673 vector<Real> barriers = {f.barrierLow, f.barrierHigh};
1674 vector<TradeBarrier> tradeBarriers;
1675 tradeBarriers.push_back(TradeBarrier(f.barrierLow, ""));
1676 tradeBarriers.push_back(TradeBarrier(f.barrierHigh, ""));
1677 BarrierData barrierData(f.barrierType, barriers, 0.0, tradeBarriers);
1678
1679 Envelope env("CP1");
1680
1681 vector<double> amounts = {f.cash};
1682 vector<string> dates = {"2016-08-01"};
1683
1684 LegData legData(QuantLib::ext::make_shared<CashflowData>(amounts, dates), true, payoutCcy);
1685 legData.isPayer() = false;
1686 ore::data::Swap swap(env, {legData});
1687 FxDoubleTouchOption doubleTouchOption(env, optionData, barrierData, "EUR", "JPY", payoutCcy, f.cash,
1688 "20160201", "TARGET", fxIndex);
1689
1690 // Build and price
1691 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1692 engineData->model("FxDoubleTouchOption") = "GarmanKohlhagen";
1693 engineData->engine("FxDoubleTouchOption") = "AnalyticDoubleBarrierBinaryEngine";
1694 engineData->model("Swap") = "DiscountedCashflows";
1695 engineData->engine("Swap") = "DiscountingSwapEngine";
1696
1697 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1698
1699 doubleTouchOption.build(engineFactory);
1700 swap.build(engineFactory);
1701
1702 // Check NPV matches expected values.
1703 if (f.barrierType == "KnockIn")
1704 BOOST_CHECK_CLOSE(doubleTouchOption.instrument()->NPV(), swap.instrument()->NPV(), 0.01);
1705 else
1706 BOOST_CHECK_CLOSE(doubleTouchOption.instrument()->NPV(), 0.0, 0.01);
1707 }
1708 }
1709 Settings::instance().evaluationDate() = today; // reset
1710 IndexManager::instance().clearHistories(); // reset
1711 }
1712}
1713
1714BOOST_AUTO_TEST_CASE(testFXEuropeanBarrierOptionSymmetry) {
1715 BOOST_TEST_MESSAGE("Testing FXEuropeanBarrierOption Symmetry...");
1716 // For single barrier options the symmetry between put and call options is:
1717 // c_di(spot, strike, barrier, r, q, vol) = p_ui(strike, spot, strike*spot/barrier, q, r, vol);
1718
1719 BarrierOptionData fxb[] = {
1720 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
1721 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1722 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1723 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1724 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1725 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1726 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1727 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1728 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1729 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1730 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1731 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1732 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1733 };
1734
1735 for (auto& f : fxb) {
1736 // build market
1737 QuantLib::ext::shared_ptr<Market> marketCall = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1738 QuantLib::ext::shared_ptr<Market> marketPut = QuantLib::ext::make_shared<TestMarket>(f.k, f.r, f.q, f.v);
1739 Date today = Settings::instance().evaluationDate();
1740 Settings::instance().evaluationDate() = marketCall->asofDate();
1741
1742 // build FXBarrierOptions - expiry in 6 months
1743 OptionData optionCallData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1744 OptionData optionPutData("Long", "Put", "European", true, vector<string>(1, "20160801"));
1745 vector<Real> barriersCall = {f.barrier};
1746 vector<TradeBarrier> tradeBarriers_call;
1747 tradeBarriers_call.push_back(TradeBarrier(f.barrier, ""));
1748 vector<Real> barriersPut = {f.s * f.k / f.barrier};
1749 vector<TradeBarrier> tradeBarriers_put;
1750 tradeBarriers_put.push_back(TradeBarrier(f.s * f.k / f.barrier, ""));
1751 BarrierData barrierCallData("DownAndIn", barriersCall, f.rebate, tradeBarriers_call);
1752 BarrierData barrierPutData("UpAndIn", barriersPut, f.rebate, tradeBarriers_put);
1753 Envelope env("CP1");
1754
1755 FxEuropeanBarrierOption fxCallOption(env, optionCallData, barrierCallData, "EUR", 1, // foreign
1756 "JPY", f.k); // domestic
1757 FxEuropeanBarrierOption fxPutOption(env, optionPutData, barrierPutData, "EUR", 1, // foreign
1758 "JPY", f.s); // domestic
1759
1760 // Build and price
1761 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1762 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
1763 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
1764 engineData->model("FxOption") = "GarmanKohlhagen";
1765 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1766
1767 QuantLib::ext::shared_ptr<EngineFactory> engineFactoryCall = QuantLib::ext::make_shared<EngineFactory>(engineData, marketCall);
1768 QuantLib::ext::shared_ptr<EngineFactory> engineFactoryPut = QuantLib::ext::make_shared<EngineFactory>(engineData, marketPut);
1769
1770 fxCallOption.build(engineFactoryCall);
1771 fxPutOption.build(engineFactoryPut);
1772
1773 Real npvCall = fxCallOption.instrument()->NPV();
1774 Real npvPut = fxPutOption.instrument()->NPV();
1775
1776 BOOST_TEST_MESSAGE("NPV Currency " << fxCallOption.npvCurrency());
1777 BOOST_TEST_MESSAGE("FX Barrier Option, NPV Call " << npvCall);
1778 BOOST_TEST_MESSAGE("FX Barrier Option, NPV Put " << npvPut);
1779 // Check NPV matches expected values.
1780 BOOST_TEST(npvCall >= 0);
1781 BOOST_TEST(npvPut >= 0);
1782 BOOST_CHECK_CLOSE(npvCall, npvPut, 0.01);
1783
1784 Settings::instance().evaluationDate() = today; // reset
1785 }
1786}
1787
1788BOOST_AUTO_TEST_CASE(testFXEuropeanBarrierOptionParity) {
1789 BOOST_TEST_MESSAGE("Testing FXEuropeanBarrierOption Parity...");
1790
1791 // An "In" option and an "Out" option with the same strikes and expiries should have the same combined price as a
1792 // vanilla Call option
1793 BarrierOptionData fxb[] = {
1794 // barrierType, barrier, rebate, type, strk, s, q, r, t, v, result
1795 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1796 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1797 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1798 {"", 100.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1799 {"", 100.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1800 {"", 100.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.25, 0.0},
1801 {"", 95.0, 0.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1802 {"", 95.0, 0.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1803 {"", 95.0, 0.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1804 {"", 100.0, 3.0, "", 90, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1805 {"", 100.0, 3.0, "", 100, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1806 {"", 100.0, 3.0, "", 110, 100.0, 0.04, 0.08, 0.50, 0.30, 0.0},
1807 };
1808
1809 vector<string> optionTypes = {"Call", "Put"};
1810 for (auto& f : fxb) {
1811 for (auto& optionType : optionTypes) {
1812 // build market
1813 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v);
1814 Date today = Settings::instance().evaluationDate();
1815 Settings::instance().evaluationDate() = market->asofDate();
1816
1817 // build FXBarrierOption - expiry in 6 months
1818 OptionData optionData("Long", optionType, "European", true, vector<string>(1, "20160801"));
1819
1820 vector<Real> barriers = {f.barrier};
1821 vector<TradeBarrier> tradeBarriers;
1822 tradeBarriers.push_back(TradeBarrier(f.barrier, ""));
1823
1824 BarrierData downInData("DownAndIn", barriers, f.rebate, tradeBarriers);
1825 BarrierData upInData("UpAndIn", barriers, f.rebate, tradeBarriers);
1826 BarrierData downOutData("DownAndOut", barriers, f.rebate, tradeBarriers);
1827 BarrierData upOutData("UpAndOut", barriers, f.rebate, tradeBarriers);
1828
1829 Envelope env("CP1");
1830
1831 FxOption fxOption(env, optionData, "EUR", 1, // foreign
1832 "JPY", f.k);
1833
1834 FxEuropeanBarrierOption downInOption(env, optionData, downInData, "EUR", 1, // foreign
1835 "JPY", f.k); // domestic
1836 FxEuropeanBarrierOption upInOption(env, optionData, upInData, "EUR", 1, // foreign
1837 "JPY", f.k); // domestic
1838 FxEuropeanBarrierOption downOutOption(env, optionData, downOutData, "EUR", 1, // foreign
1839 "JPY", f.k); // domestic
1840 FxEuropeanBarrierOption upOutOption(env, optionData, upOutData, "EUR", 1, // foreign
1841 "JPY", f.k); // domestic
1842
1843 vector<double> amounts = {f.rebate};
1844 vector<string> dates = {"2016-08-01"};
1845 LegData legData(QuantLib::ext::make_shared<CashflowData>(amounts, dates), false, "JPY");
1846 ore::data::Swap swap(env, {legData});
1847
1848 // Build and price
1849 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1850 engineData->model("FxDigitalOption") = "GarmanKohlhagen";
1851 engineData->engine("FxDigitalOption") = "AnalyticEuropeanEngine";
1852 engineData->model("FxOption") = "GarmanKohlhagen";
1853 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1854 engineData->model("Swap") = "DiscountedCashflows";
1855 engineData->engine("Swap") = "DiscountingSwapEngine";
1856
1857 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1858
1859 fxOption.build(engineFactory);
1860 downInOption.build(engineFactory);
1861 upInOption.build(engineFactory);
1862 downOutOption.build(engineFactory);
1863 upOutOption.build(engineFactory);
1864 swap.build(engineFactory);
1865
1866 Real npv = fxOption.instrument()->NPV() + swap.instrument()->NPV();
1867
1868 // Check NPV matches expected values.
1869 BOOST_TEST(downInOption.instrument()->NPV() >= 0);
1870 BOOST_TEST(downOutOption.instrument()->NPV() >= 0);
1871 BOOST_TEST(upInOption.instrument()->NPV() >= 0);
1872 BOOST_TEST(upOutOption.instrument()->NPV() >= 0);
1873 BOOST_CHECK_CLOSE(npv, downInOption.instrument()->NPV() + downOutOption.instrument()->NPV(), 0.01);
1874 BOOST_CHECK_CLOSE(npv, upInOption.instrument()->NPV() + upOutOption.instrument()->NPV(), 0.01);
1875
1876 Settings::instance().evaluationDate() = today; // reset
1877 }
1878 }
1879}
1880
1881BOOST_AUTO_TEST_CASE(testFXKIKOBarrierOption) {
1882 BOOST_TEST_MESSAGE("Testing FXDoubleBarrierOption when barrier already touched...");
1883
1884 struct KIKOBarrierOptionData {
1885 string knockInType;
1886 string knockOutType;
1887 Real barrierKnockIn;
1888 Real barrierKnockOut;
1889 Real rebate;
1890 string type;
1891 Real k;
1892 Real s; // spot
1893 Rate q; // dividend
1894 Rate r; // risk-free rate
1895 Real t; // time to maturity
1896 Volatility v; // volatility
1897 };
1898
1899 KIKOBarrierOptionData fxdb[] = {
1900 // knockInBarrierType, knockOutBarrierType, barrierKnockIn, barrierKnockOut, rebate, type, k, s, q,
1901 // r, t, v
1902 {"DownAndIn", "UpAndOut", 80.0, 120.0, 0.0, "Call", 100.0, 100.0, 0.04, 0.08, 0.50, 0.2},
1903 {"UpAndIn", "UpAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 80.0, 0.04, 0.08, 0.50, 0.2},
1904 {"UpAndIn", "DownAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 80.0, 0.04, 0.08, 0.50, 0.2},
1905 {"DownAndIn", "DownAndOut", 100.0, 80.0, 0.0, "Call", 100.0, 120.0, 0.04, 0.08, 0.50, 0.2}};
1906
1907 // we test that the trades knocks in and knocks out as expected when seasoned
1908 for (auto& f : fxdb) {
1909 // build market
1910 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
1911 Date today = Settings::instance().evaluationDate();
1912 Settings::instance().evaluationDate() = today; // reset
1913 Settings::instance().evaluationDate() = market->asofDate();
1914
1915 // build FXBarrierOption - expiry in 6 months
1916 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1917 vector<TradeBarrier> tradeBarriers_KI;
1918 tradeBarriers_KI.push_back(TradeBarrier(f.barrierKnockIn, ""));
1919 vector<TradeBarrier> tradeBarriers_KO;
1920 tradeBarriers_KO.push_back(TradeBarrier(f.barrierKnockOut, ""));
1921 BarrierData knockInBarrierData(f.knockInType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
1922 BarrierData knockOutBarrierData(f.knockOutType, {f.barrierKnockOut}, 0.0, tradeBarriers_KO);
1923
1924 vector<BarrierData> barriers = {knockInBarrierData, knockOutBarrierData};
1925 Envelope env("CP1");
1926
1927 FxKIKOBarrierOption kikoBarrierOption(env, optionData, barriers, "EUR", 1, "JPY", f.k, "20160201", "TARGET",
1928 "FX-Reuters-EUR-JPY");
1929 FxBarrierOption koBarrierOption(env, optionData, knockOutBarrierData, Date(1, Month::February, 2016), "TARGET", "EUR", 1,
1930 "JPY", f.k, "FX-Reuters-EUR-JPY");
1931
1932 FxOption fxOption(env, optionData, "EUR", 1, // foreign
1933 "JPY", f.k); // domestic
1934
1935 // Build and price
1936 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
1937 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
1938 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
1939 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
1940 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
1941 engineData->model("FxOption") = "GarmanKohlhagen";
1942 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
1943
1944 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
1945
1946 // knocked in npv = knockOut npv
1947 TimeSeries<Real> pastFixings;
1948 pastFixings[market->asofDate() - 1 * Days] = f.barrierKnockIn;
1949 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
1950
1951 kikoBarrierOption.reset();
1952 kikoBarrierOption.build(engineFactory);
1953 koBarrierOption.build(engineFactory);
1954 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), koBarrierOption.instrument()->NPV(), 0.01);
1955 // knocked out npv = 0
1956 IndexManager::instance().clearHistories(); // reset
1957 TimeSeries<Real> pastFixings2;
1958 pastFixings2[market->asofDate() - 1 * Days] = f.barrierKnockIn;
1959 pastFixings2[market->asofDate() - 2 * Days] = f.barrierKnockOut;
1960 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings2);
1961 kikoBarrierOption.reset();
1962 kikoBarrierOption.build(engineFactory);
1963
1964 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), 0.0, 0.01);
1965
1966 IndexManager::instance().clearHistories(); // reset
1967 }
1968
1969 KIKOBarrierOptionData fxdb3[] = {
1970 // knockInBarrierType, knockOutBarrierType, barrierKnockIn, barrierKnockOut, rebate, type, k, s, q,
1971 // r, t, v
1972 {"DownAndIn", "UpAndOut", 80.0, 120.0, 0.0, "Call", 100.0, 79.0, 0.04, 0.08, 0.50, 0.2},
1973 {"UpAndIn", "UpAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 101.0, 0.04, 0.08, 0.50, 0.2},
1974 {"UpAndIn", "DownAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 101.0, 0.04, 0.08, 0.50, 0.2},
1975 {"DownAndIn", "DownAndOut", 100.0, 80.0, 0.0, "Call", 100.0, 99.0, 0.04, 0.08, 0.50, 0.2}};
1976
1977 // we test trades that are knocked in but unseasoned
1978 for (auto& f : fxdb3) {
1979 // build market
1980 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
1981 Date today = Settings::instance().evaluationDate();
1982 Settings::instance().evaluationDate() = today; // reset
1983 Settings::instance().evaluationDate() = market->asofDate();
1984
1985 // build FXBarrierOption - expiry in 6 months
1986 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
1987 vector<TradeBarrier> tradeBarriers_KI;
1988 tradeBarriers_KI.push_back(TradeBarrier(f.barrierKnockIn, ""));
1989 vector<TradeBarrier> tradeBarriers_KO;
1990 tradeBarriers_KO.push_back(TradeBarrier(f.barrierKnockOut, ""));
1991 BarrierData knockInBarrierData(f.knockInType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
1992 BarrierData knockOutBarrierData(f.knockOutType, {f.barrierKnockOut}, 0.0, tradeBarriers_KO);
1993
1994 vector<BarrierData> barriers = {knockInBarrierData, knockOutBarrierData};
1995 Envelope env("CP1");
1996
1997 FxKIKOBarrierOption kikoBarrierOption(env, optionData, barriers, "EUR", 1, "JPY", f.k, "20160201", "TARGET",
1998 "FX-Reuters-EUR-JPY");
1999 FxBarrierOption koBarrierOption(env, optionData, knockOutBarrierData, Date(1, Month::February, 2016), "TARGET",
2000 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2001
2002 FxOption fxOption(env, optionData, "EUR", 1, // foreign
2003 "JPY", f.k); // domestic
2004
2005 // Build and price
2006 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
2007 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
2008 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
2009 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
2010 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
2011 engineData->model("FxOption") = "GarmanKohlhagen";
2012 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
2013
2014 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
2015
2016 kikoBarrierOption.build(engineFactory);
2017 koBarrierOption.build(engineFactory);
2018 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), koBarrierOption.instrument()->NPV(), 0.01);
2019 }
2020
2021 KIKOBarrierOptionData fxdb4[] = {
2022 // knockInBarrierType, knockOutBarrierType, barrierKnockIn, barrierKnockOut, rebate, type, k, s, q,
2023 // r, t, v
2024 {"DownAndIn", "UpAndOut", 80.0, 120.0, 0.0, "Call", 120.0, 121.0, 0.04, 0.08, 0.50, 0.2},
2025 {"UpAndIn", "UpAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 121.0, 0.04, 0.08, 0.50, 0.2},
2026 {"UpAndIn", "DownAndOut", 100.0, 120.0, 0.0, "Call", 100.0, 119.0, 0.04, 0.08, 0.50, 0.2},
2027 {"DownAndIn", "DownAndOut", 100.0, 80.0, 0.0, "Call", 100.0, 79.0, 0.04, 0.08, 0.50, 0.2}};
2028
2029 // we test trades that are knocked out but unseasoned
2030 for (auto& f : fxdb4) {
2031 // build market
2032 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
2033 Date today = Settings::instance().evaluationDate();
2034 Settings::instance().evaluationDate() = today; // reset
2035 Settings::instance().evaluationDate() = market->asofDate();
2036
2037 // build FXBarrierOption - expiry in 6 months
2038 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
2039 vector<TradeBarrier> tradeBarriers_KI;
2040 tradeBarriers_KI.push_back(TradeBarrier(f.barrierKnockIn, ""));
2041 vector<TradeBarrier> tradeBarriers_KO;
2042 tradeBarriers_KO.push_back(TradeBarrier(f.barrierKnockOut, ""));
2043 BarrierData knockInBarrierData(f.knockInType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
2044 BarrierData knockOutBarrierData(f.knockOutType, {f.barrierKnockOut}, 0.0, tradeBarriers_KO);
2045
2046 vector<BarrierData> barriers = {knockInBarrierData, knockOutBarrierData};
2047 Envelope env("CP1");
2048
2049 FxKIKOBarrierOption kikoBarrierOption(env, optionData, barriers, "EUR", 1, "JPY", f.k, "20160201", "TARGET",
2050 "FX-Reuters-EUR-JPY");
2051 FxBarrierOption koBarrierOption(env, optionData, knockOutBarrierData, Date(1, Month::February, 2016), "TARGET",
2052 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2053
2054 FxOption fxOption(env, optionData, "EUR", 1, // foreign
2055 "JPY", f.k); // domestic
2056
2057 // Build and price
2058 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
2059 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
2060 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
2061 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
2062 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
2063 engineData->model("FxOption") = "GarmanKohlhagen";
2064 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
2065
2066 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
2067
2068 // knocked out npv = 0
2069 kikoBarrierOption.build(engineFactory);
2070
2071 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), 0.0, 0.01);
2072 }
2073
2074 // we also test the cases where the knockOut barrier is an extreme value, unlikely to be triggered.
2075 // In this case we expect Kiko_npv == ki_npv
2076 KIKOBarrierOptionData fxdb2[] = {
2077 // knockInBarrierType, knockOutBarrierType, barrierKnockIn, barrierKnockOut, rebate, type, k, s, q,
2078 // r, t
2079 {"DownAndIn", "UpAndOut", 80.0, 1000000.0, 0.0, "Call", 100.0, 100.0, 0.04, 0.08, 0.50, 0.2},
2080 {"UpAndIn", "UpAndOut", 150.0, 1000000.0, 0.0, "Call", 100.0, 80.0, 0.04, 0.08, 0.50, 0.2},
2081 {"UpAndIn", "DownAndOut", 150.0, 0.000001, 0.0, "Call", 100.0, 80.0, 0.04, 0.08, 0.50, 0.2},
2082 {"DownAndIn", "DownAndOut", 100.0, 0.000001, 0.0, "Call", 100.0, 120.0, 0.04, 0.08, 0.50, 0.2}};
2083
2084 for (auto& f : fxdb2) {
2085 BOOST_TEST_MESSAGE("testing " << f.knockInType << " " << f.knockOutType);
2086 // build market
2087 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
2088 Date today = Settings::instance().evaluationDate();
2089 Settings::instance().evaluationDate() = today; // reset
2090 Settings::instance().evaluationDate() = market->asofDate();
2091
2092 // build FXBarrierOption - expiry in 6 months
2093 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
2094 vector<TradeBarrier> tradeBarriers_KI;
2095 tradeBarriers_KI.push_back(TradeBarrier(f.barrierKnockIn, ""));
2096 vector<TradeBarrier> tradeBarriers_KO;
2097 tradeBarriers_KO.push_back(TradeBarrier(f.barrierKnockOut, ""));
2098 BarrierData knockInBarrierData(f.knockInType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
2099 BarrierData knockOutBarrierData(f.knockOutType, {f.barrierKnockOut}, 0.0, tradeBarriers_KO);
2100 BarrierData knockOutBarrierData2(f.knockOutType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
2101
2102 vector<BarrierData> barriers = {knockInBarrierData, knockOutBarrierData};
2103 Envelope env("CP1");
2104
2105 FxKIKOBarrierOption kikoBarrierOption(env, optionData, barriers, "EUR", 1, "JPY", f.k, "20160201", "TARGET",
2106 "FX-Reuters-EUR-JPY");
2107 FxBarrierOption kiBarrierOption(env, optionData, knockInBarrierData, Date(1, Month::February, 2016), "TARGET",
2108 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2109 FxBarrierOption koBarrierOption(env, optionData, knockOutBarrierData, Date(1, Month::February, 2016), "TARGET",
2110 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2111 FxBarrierOption koBarrierOption2(env, optionData, knockOutBarrierData2, Date(1, Month::February, 2016),
2112 "TARGET", "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2113
2114 vector<Real> barrier = {std::min(f.barrierKnockIn, f.barrierKnockOut),
2115 std::max(f.barrierKnockIn, f.barrierKnockOut)};
2116 vector<TradeBarrier> tradeBarriers = {TradeBarrier(std::min(f.barrierKnockIn, f.barrierKnockOut), ""),
2117 TradeBarrier(std::max(f.barrierKnockIn, f.barrierKnockOut), "")};
2118 BarrierData barrierData("KnockOut", barrier, 0, tradeBarriers);
2119
2120 FxDoubleBarrierOption dkoBarrierOption(env, optionData, barrierData, Date(), "", "EUR", 1, // foreign
2121 "JPY", f.k); // domestic
2122
2123 FxOption fxOption(env, optionData, "EUR", 1, // foreign
2124 "JPY", f.k); // domestic
2125
2126 // Build and price
2127 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
2128 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
2129 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
2130 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
2131 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
2132 engineData->model("FxOption") = "GarmanKohlhagen";
2133 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
2134
2135 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
2136
2137 TimeSeries<Real> pastFixings;
2138 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
2139 // untouched kiko_npv = untouched ki_npv
2140 kikoBarrierOption.build(engineFactory);
2141 kiBarrierOption.build(engineFactory);
2142 koBarrierOption.build(engineFactory);
2143 koBarrierOption2.build(engineFactory);
2144 dkoBarrierOption.build(engineFactory);
2145 fxOption.build(engineFactory);
2146
2147 BOOST_TEST_MESSAGE("KIKO NPV: " << kikoBarrierOption.instrument()->NPV());
2148 BOOST_TEST_MESSAGE("KI NPV: " << kiBarrierOption.instrument()->NPV());
2149 BOOST_TEST_MESSAGE("KO(knockoutLevel) NPV: " << koBarrierOption.instrument()->NPV());
2150 BOOST_TEST_MESSAGE("KO(knockinLevel) NPV: " << koBarrierOption2.instrument()->NPV());
2151 BOOST_TEST_MESSAGE("DoubleKnockOut NPV: " << dkoBarrierOption.instrument()->NPV());
2152 BOOST_TEST_MESSAGE("FXOption NPV: " << fxOption.instrument()->NPV());
2153
2154 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), kiBarrierOption.instrument()->NPV(), 0.01);
2155
2156 // knocked in kiko_npv = knocked in ki_npv
2157 pastFixings[market->asofDate() - 1 * Days] = f.barrierKnockIn;
2158 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
2159
2160 kikoBarrierOption.reset();
2161 kiBarrierOption.reset();
2162 dkoBarrierOption.reset();
2163 kikoBarrierOption.build(engineFactory);
2164 kiBarrierOption.build(engineFactory);
2165 fxOption.build(engineFactory);
2166
2167 kikoBarrierOption.build(engineFactory);
2168 kiBarrierOption.build(engineFactory);
2169 koBarrierOption.build(engineFactory);
2170 dkoBarrierOption.build(engineFactory);
2171 fxOption.build(engineFactory);
2172
2173 BOOST_TEST_MESSAGE("KIKO NPV: " << kikoBarrierOption.instrument()->NPV());
2174 BOOST_TEST_MESSAGE("KI NPV: " << kiBarrierOption.instrument()->NPV());
2175 BOOST_TEST_MESSAGE("KO(knockoutLevel) NPV: " << koBarrierOption.instrument()->NPV());
2176 BOOST_TEST_MESSAGE("KO(knockinLevel) NPV: " << koBarrierOption2.instrument()->NPV());
2177 BOOST_TEST_MESSAGE("DoubleKnockOut NPV: " << dkoBarrierOption.instrument()->NPV());
2178 BOOST_TEST_MESSAGE("FXOption NPV: " << fxOption.instrument()->NPV());
2179
2180
2181 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), kiBarrierOption.instrument()->NPV(), 0.01);
2182
2183 IndexManager::instance().clearHistories(); // reset
2184 }
2185
2186
2187 // we test that when the spot value is updated the trade behaves as expected.
2188 KIKOBarrierOptionData fxdb5[] = {
2189 // knockInBarrierType, knockOutBarrierType, barrierKnockIn, barrierKnockOut, rebate, type, k, s, q,
2190 // r, t
2191 {"UpAndIn", "UpAndOut", 80.0, 150.0, 0.0, "Call", 100.0, 70.0, 0.04, 0.08, 0.50, 0.2},
2192 {"DownAndIn", "DownAndOut", 150.0, 80, 0.0, "Call", 100.0, 160.0, 0.04, 0.08, 0.50, 0.2}};
2193
2194 for (auto& f : fxdb5) {
2195 BOOST_TEST_MESSAGE("testing " << f.knockInType << " " << f.knockOutType);
2196 // build market
2197 QuantLib::ext::shared_ptr<Market> market = QuantLib::ext::make_shared<TestMarket>(f.s, f.q, f.r, f.v, true);
2198 Date today = Settings::instance().evaluationDate();
2199 Settings::instance().evaluationDate() = today; // reset
2200 Settings::instance().evaluationDate() = market->asofDate();
2201
2202 // build FXBarrierOption - expiry in 6 months
2203 OptionData optionData("Long", "Call", "European", true, vector<string>(1, "20160801"));
2204 vector<TradeBarrier> tradeBarriers_KI = {TradeBarrier(f.barrierKnockIn, "")};
2205 vector<TradeBarrier> tradeBarriers_KO = {TradeBarrier(f.barrierKnockOut, "")};
2206 BarrierData knockInBarrierData(f.knockInType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
2207 BarrierData knockOutBarrierData(f.knockOutType, {f.barrierKnockOut}, 0.0, tradeBarriers_KO);
2208 BarrierData knockOutBarrierData2(f.knockOutType, {f.barrierKnockIn}, 0.0, tradeBarriers_KI);
2209
2210 vector<BarrierData> barriers = {knockInBarrierData, knockOutBarrierData};
2211 Envelope env("CP1");
2212
2213 FxKIKOBarrierOption kikoBarrierOption(env, optionData, barriers, "EUR", 1, "JPY", f.k, "20160201", "TARGET",
2214 "FX-Reuters-EUR-JPY");
2215 FxBarrierOption kiBarrierOption(env, optionData, knockInBarrierData, Date(1, Month::February, 2016), "TARGET",
2216 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2217 FxBarrierOption koBarrierOption(env, optionData, knockOutBarrierData, Date(1, Month::February, 2016), "TARGET",
2218 "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2219 FxBarrierOption koBarrierOption2(env, optionData, knockOutBarrierData2, Date(1, Month::February, 2016),
2220 "TARGET", "EUR", 1, "JPY", f.k, "FX-Reuters-EUR-JPY");
2221
2222 vector<Real> barrier = {std::min(f.barrierKnockIn, f.barrierKnockOut),
2223 std::max(f.barrierKnockIn, f.barrierKnockOut)};
2224 vector<TradeBarrier> tradeBarriers = {TradeBarrier(std::min(f.barrierKnockIn, f.barrierKnockOut), ""),
2225 TradeBarrier(std::max(f.barrierKnockIn, f.barrierKnockOut), "")};
2226 BarrierData barrierData("KnockOut", barrier, 0, tradeBarriers);
2227
2228 FxDoubleBarrierOption dkoBarrierOption(env, optionData, barrierData, Date(), "",
2229 "EUR", 1, // foreign
2230 "JPY", f.k); // domestic
2231
2232 FxOption fxOption(env, optionData, "EUR", 1, // foreign
2233 "JPY", f.k); // domestic
2234
2235 // Build and price
2236 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
2237 engineData->model("FxDoubleBarrierOption") = "GarmanKohlhagen";
2238 engineData->engine("FxDoubleBarrierOption") = "AnalyticDoubleBarrierEngine";
2239 engineData->model("FxBarrierOption") = "GarmanKohlhagen";
2240 engineData->engine("FxBarrierOption") = "AnalyticBarrierEngine";
2241 engineData->model("FxOption") = "GarmanKohlhagen";
2242 engineData->engine("FxOption") = "AnalyticEuropeanEngine";
2243
2244 QuantLib::ext::shared_ptr<EngineFactory> engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
2245
2246 TimeSeries<Real> pastFixings;
2247 IndexManager::instance().setHistory("Reuters EUR/JPY", pastFixings);
2248
2249 kikoBarrierOption.build(engineFactory);
2250 kiBarrierOption.build(engineFactory);
2251 koBarrierOption.build(engineFactory);
2252 koBarrierOption2.build(engineFactory);
2253 dkoBarrierOption.build(engineFactory);
2254 fxOption.build(engineFactory);
2255
2256 BOOST_TEST_MESSAGE("KIKO NPV: " << kikoBarrierOption.instrument()->NPV());
2257 BOOST_TEST_MESSAGE("KI NPV: " << kiBarrierOption.instrument()->NPV());
2258 BOOST_TEST_MESSAGE("KO(knockoutLevel) NPV: " << koBarrierOption.instrument()->NPV());
2259 BOOST_TEST_MESSAGE("KO(knockinLevel) NPV: " << koBarrierOption2.instrument()->NPV());
2260 BOOST_TEST_MESSAGE("DoubleKnockOut NPV: " << dkoBarrierOption.instrument()->NPV());
2261 BOOST_TEST_MESSAGE("FXOption NPV: " << fxOption.instrument()->NPV());
2262
2263 //check trade knockedIn
2264 dynamic_pointer_cast<TestMarket>(market)->setFxSpot("EURJPY", f.barrierKnockIn);
2265 BOOST_CHECK_CLOSE(kikoBarrierOption.instrument()->NPV(), koBarrierOption.instrument()->NPV(), 0.01);
2266
2267 //check trade knockedOut
2268 dynamic_pointer_cast<TestMarket>(market)->setFxSpot("EURJPY", f.barrierKnockOut);
2269 BOOST_CHECK_SMALL(kikoBarrierOption.instrument()->NPV(), 0.0001);
2270
2271 IndexManager::instance().clearHistories(); // reset
2272 }
2273
2274}
2275
2276BOOST_AUTO_TEST_SUITE_END()
2277
2278BOOST_AUTO_TEST_SUITE_END()
Engine builder for FX Forwards.
Engine builder for FX Options.
Engine builder for Swaps.
Serializable obejct holding barrier data.
Definition: barrierdata.hpp:34
Serializable object holding generic trade data, reporting dimensions.
Definition: envelope.hpp:51
Serializable FX Barrier Option.
Serializable FX Digital Barrier Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable FX Digital Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable FX Double Barrier Option.
Serializable FX Double One-Touch/No-Touch Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable FX European Barrier Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable FX KIKO Barrier Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable FX Option.
Definition: fxoption.hpp:38
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Definition: fxoption.cpp:41
void build(const QuantLib::ext::shared_ptr< ore::data::EngineFactory > &ef) override
Serializable FX One-Touch/No-Touch Option.
void build(const QuantLib::ext::shared_ptr< EngineFactory > &) override
Build QuantLib/QuantExt instrument, link pricing engine.
Serializable object holding leg data.
Definition: legdata.hpp:844
bool isPayer() const
Definition: legdata.hpp:872
static const string defaultConfiguration
Default configuration label.
Definition: market.hpp:296
Market Implementation.
Definition: marketimpl.hpp:53
map< pair< string, string >, Handle< BlackVolTermStructure > > fxVols_
Definition: marketimpl.hpp:214
QuantLib::ext::shared_ptr< FXTriangulation > fx_
Definition: marketimpl.hpp:206
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
Definition: marketimpl.hpp:208
Serializable object holding option data.
Definition: optiondata.hpp:42
Serializable Swap, Single and Cross Currency.
Definition: swap.hpp:36
const QuantLib::ext::shared_ptr< InstrumentWrapper > & instrument() const
Definition: trade.hpp:141
const string & npvCurrency() const
Definition: trade.hpp:149
void reset()
Reset trade, clear all base class data. This does not reset accumulated timings for this trade.
Definition: trade.cpp:130
A class to hold pricing engine parameters.
FX Barrier Option data model and serialization.
FX Digital Option data model and serialization.
FX Double Barrier Option data model and serialization.
FX Double One-Touch/No-Touch Option data model and serialization.
FX European Barrier Option data model and serialization.
BOOST_AUTO_TEST_CASE(testFXDigitalOptionPrice)
Definition: fxexotics.cpp:136
FX Forward data model and serialization.
FX Option data model and serialization.
FX One-Touch/No-Touch 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)
Definition: ast.cpp:28
Swap trade data model and serialization.
vector< Option::Type > optionTypes