Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fxasianoption.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 Skandinaviska Enskilda Banken AB (publ)
3 All rights reserved.
4 This file is part of ORE, a free-software/open-source library
5 for transparent pricing and risk analysis - http://opensourcerisk.org
6 ORE is free software: you can redistribute it and/or modify it
7 under the terms of the Modified BSD License. You should have received a
8 copy of the license along with this program.
9 The license is also available online at <http://opensourcerisk.org>
10 This program is distributed on the basis that it will form a useful
11 contribution to risk analytics and model standardisation, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
14 */
15
16 #include <boost/test/unit_test.hpp>
17 #include <oret/toplevelfixture.hpp>
18
19 #include <boost/make_shared.hpp>
20
21 #include <ql/currencies/america.hpp>
22 #include <ql/instruments/asianoption.hpp>
23 #include <ql/math/interpolations/linearinterpolation.hpp>
24 #include <ql/termstructures/yield/flatforward.hpp>
25 #include <ql/time/daycounters/actual360.hpp>
26
32
33 using namespace std;
34 using namespace boost::unit_test_framework;
35 using namespace boost::algorithm;
36 using namespace QuantLib;
37 using namespace QuantExt;
38 using namespace ore::data;
39
40 namespace {
41
42 class TestMarket : public MarketImpl {
43 public:
44 TestMarket(const Real spot, const Date& expiry, const Rate domRate, const Rate forRate,
45 const Volatility flatVolatility)
46 : MarketImpl(false) {
47 // Reference date and common day counter
48 asof_ = Date(01, Feb, 2021);
49 DayCounter dayCounter = Actual360();
50
51 // Add USD/JPY discount curves
52 Handle<YieldTermStructure> domestic(QuantLib::ext::make_shared<FlatForward>(asof_, domRate, dayCounter));
53 Handle<YieldTermStructure> foreign(QuantLib::ext::make_shared<FlatForward>(asof_, forRate, dayCounter));
54 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "USD")] = domestic;
55 yieldCurves_[make_tuple(Market::defaultConfiguration, YieldCurveType::Discount, "JPY")] = foreign;
56
57 // Add fx spot
58 std::map<std::string, Handle<Quote>> quotes;
59 quotes["JPYUSD"] = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot));
60 fx_ = QuantLib::ext::make_shared<FXTriangulation>(quotes);
61
62 // Add USDJPY volatilities
63 Handle<BlackVolTermStructure> volatility(
64 QuantLib::ext::make_shared<BlackConstantVol>(asof_, TARGET(), flatVolatility, dayCounter));
65 fxVols_[make_pair(Market::defaultConfiguration, "JPYUSD")] = volatility;
66 }
67 };
68
69 struct DiscreteAsianTestData {
70 Option::Type type;
71 Real spot;
72 Real strike;
73 Rate foreignRate;
74 Rate domesticRate;
75 Time firstFixing;
76 Time length;
77 Size fixings;
78 Volatility volatility;
79 Real expectedNPV;
80 };
81
82 } // namespace
83
84 BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
85
86 BOOST_AUTO_TEST_SUITE(FxAsianOptionTests)
87
88 BOOST_AUTO_TEST_CASE(testFxAsianOptionTradeBuilding) {
89
90 BOOST_TEST_MESSAGE("Testing FX Asian option trade building with constant vol term structure");
91
92 // Data from "Asian Option", Levy, 1997 in "Exotic Options: The State of the Art",
93 // edited by Clewlow, Strickland
94 // Tests with > 100 fixings are skipped here for speed, QL already tests these
95 std::vector<DiscreteAsianTestData> asians = {
96 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 2, 0.13, 1.3942835683},
97 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 4, 0.13, 1.5852442983},
98 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 8, 0.13, 1.66970673},
99 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 12, 0.13, 1.6980019214},
100 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 26, 0.13, 1.7255070456},
101 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 52, 0.13, 1.7401553533},
102 {Option::Put, 90.0, 87.0, 0.06, 0.025, 0.0, 11.0 / 12.0, 100, 0.13, 1.7478303712},
103 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 2, 0.13, 1.8496053697},
104 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 4, 0.13, 2.0111495205},
105 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 8, 0.13, 2.0852138818},
106 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 12, 0.13, 2.1105094397},
107 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 26, 0.13, 2.1346526695},
108 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 52, 0.13, 2.147489651},
109 {Option::Put, 90.0, 87.0, 0.06, 0.025, 1.0 / 12.0, 11.0 / 12.0, 100, 0.13, 2.154728109},
110 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 2, 0.13, 2.63315092584},
111 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 4, 0.13, 2.76723962361},
112 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 8, 0.13, 2.83124836881},
113 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 12, 0.13, 2.84290301412},
114 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 26, 0.13, 2.88179560417},
115 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 52, 0.13, 2.88447044543},
116 {Option::Put, 90.0, 87.0, 0.06, 0.025, 3.0 / 12.0, 11.0 / 12.0, 100, 0.13, 2.89985329603}};
117
118 Date asof = Date(01, Feb, 2021);
119 Envelope env("CP1");
120 QuantLib::ext::shared_ptr<EngineFactory> engineFactory;
121 QuantLib::ext::shared_ptr<Market> market;
122
123 for (const auto& a : asians) {
124 Time deltaT = a.length / (a.fixings - 1);
125 Date expiry;
126 vector<Date> fixingDates(a.fixings);
127 vector<std::string> strFixingDates(a.fixings);
128 for (Size i = 0; i < a.fixings; ++i) {
129 fixingDates[i] = (asof + static_cast<Integer>((a.firstFixing + i * deltaT) * 360 + 0.5));
130 strFixingDates[i] = to_string(fixingDates[i]);
131 }
132 expiry = fixingDates[a.fixings - 1];
133
134 ScheduleDates scheduleDates("NullCalendar", "", "", strFixingDates);
135 ScheduleData scheduleData(scheduleDates);
136
137 market = QuantLib::ext::make_shared<TestMarket>(a.spot, expiry, a.domesticRate, a.foreignRate, a.volatility);
138 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
139 std::string productName = "FxAsianOptionArithmeticPrice";
140 engineData->model(productName) = "GarmanKohlhagen";
141 engineData->engine(productName) = "MCDiscreteArithmeticAPEngine";
142 engineData->engineParameters(productName) = {{"ProcessType", "Discrete"}, {"BrownianBridge", "True"},
143 {"AntitheticVariate", "False"}, {"ControlVariate", "True"},
144 {"RequiredSamples", "2047"}, {"Seed", "0"}};
145 engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
146
147 // Set evaluation date
148 Settings::instance().evaluationDate() = market->asofDate();
149
150 // Test the building of a FX Asian option doesn't throw
151 PremiumData premiumData;
152 OptionData optionData("Long", to_string(a.type), "European", true, {to_string(expiry)}, "Cash", "",
153 premiumData,
154 vector<Real>(), vector<Real>(), "", "", "", vector<string>(), vector<string>(), "", "",
155 "", "Asian", "Arithmetic", boost::none, boost::none, boost::none);
156
157 QuantLib::ext::shared_ptr<FxAsianOption> asianOption = QuantLib::ext::make_shared<FxAsianOption>(
158 env, "FxAsianOption", 1.0, TradeStrike(a.strike, "USD"), optionData, scheduleData,
159 QuantLib::ext::make_shared<FXUnderlying>("FX", "ECB-JPY-USD", 1.0), Date(), "USD");
160 BOOST_CHECK_NO_THROW(asianOption->build(engineFactory));
161
162 // Check the underlying instrument was built as expected
163 QuantLib::ext::shared_ptr<Instrument> qlInstrument = asianOption->instrument()->qlInstrument();
164
165 QuantLib::ext::shared_ptr<DiscreteAveragingAsianOption> discreteAsian =
166 QuantLib::ext::dynamic_pointer_cast<DiscreteAveragingAsianOption>(qlInstrument);
167
168 BOOST_CHECK(discreteAsian);
169 BOOST_CHECK_EQUAL(discreteAsian->exercise()->type(), Exercise::Type::European);
170 BOOST_CHECK_EQUAL(discreteAsian->exercise()->dates().size(), 1);
171 BOOST_CHECK_EQUAL(discreteAsian->exercise()->dates()[0], expiry);
172
173 QuantLib::ext::shared_ptr<TypePayoff> payoff = QuantLib::ext::dynamic_pointer_cast<TypePayoff>(discreteAsian->payoff());
174 BOOST_CHECK(payoff);
175 BOOST_CHECK_EQUAL(payoff->optionType(), a.type);
176
177 Real expectedPrice = a.expectedNPV;
178
179 // Check the price
180 BOOST_CHECK_SMALL(asianOption->instrument()->NPV() - expectedPrice, 2e-2);
181 }
182 }
183
184 BOOST_AUTO_TEST_CASE(testFxAsianOptionFromXml) {
185
186 BOOST_TEST_MESSAGE("Testing parsing of FX Asian option trade from XML");
187
188 // Create an XML string representation of the trade
189 string tradeXml;
190 tradeXml.append("<Portfolio>");
191 tradeXml.append(" <Trade id=\"FxAsianOption_USDJPY\">");
192 tradeXml.append(" <TradeType>FxAsianOption</TradeType>");
193 tradeXml.append(" <Envelope>");
194 tradeXml.append(" <CounterParty>CPTY_A</CounterParty>");
195 tradeXml.append(" <NettingSetId>CPTY_A</NettingSetId>");
196 tradeXml.append(" <AdditionalFields/>");
197 tradeXml.append(" </Envelope>");
198 tradeXml.append(" <FxAsianOptionData>");
199 tradeXml.append(" <OptionData>");
200 tradeXml.append(" <LongShort>Long</LongShort>");
201 tradeXml.append(" <OptionType>Call</OptionType>");
202 tradeXml.append(" <Style>European</Style>");
203 tradeXml.append(" <Settlement>Cash</Settlement>");
204 tradeXml.append(" <PayOffAtExpiry>false</PayOffAtExpiry>");
205 tradeXml.append(" <PayoffType>Asian</PayoffType>");
206 tradeXml.append(" <PayoffType2>Arithmetic</PayoffType2>");
207 tradeXml.append(" <ExerciseDates>");
208 tradeXml.append(" <ExerciseDate>2021-02-26</ExerciseDate>");
209 tradeXml.append(" </ExerciseDates>");
210 tradeXml.append(" </OptionData>");
211 tradeXml.append(" <ObservationDates>");
212 tradeXml.append(" <Dates>");
213 tradeXml.append(" <Dates>");
214 tradeXml.append(" <Date>2021-02-01</Date>");
215 tradeXml.append(" <Date>2021-02-02</Date>");
216 tradeXml.append(" <Date>2021-02-03</Date>");
217 tradeXml.append(" <Date>2021-02-04</Date>");
218 tradeXml.append(" <Date>2021-02-05</Date>");
219 tradeXml.append(" <Date>2021-02-08</Date>");
220 tradeXml.append(" <Date>2021-02-09</Date>");
221 tradeXml.append(" <Date>2021-02-10</Date>");
222 tradeXml.append(" <Date>2021-02-11</Date>");
223 tradeXml.append(" <Date>2021-02-12</Date>");
224 tradeXml.append(" <Date>2021-02-15</Date>");
225 tradeXml.append(" <Date>2021-02-16</Date>");
226 tradeXml.append(" <Date>2021-02-17</Date>");
227 tradeXml.append(" <Date>2021-02-18</Date>");
228 tradeXml.append(" <Date>2021-02-19</Date>");
229 tradeXml.append(" <Date>2021-02-22</Date>");
230 tradeXml.append(" <Date>2021-02-23</Date>");
231 tradeXml.append(" <Date>2021-02-24</Date>");
232 tradeXml.append(" <Date>2021-02-25</Date>");
233 tradeXml.append(" <Date>2021-02-26</Date>");
234 tradeXml.append(" </Dates>");
235 tradeXml.append(" </Dates>");
236 tradeXml.append(" </ObservationDates>");
237 tradeXml.append(" <Name>FX-ECB-USD-JPY</Name>");
238 tradeXml.append(" <Strike>104.6860</Strike>");
239 tradeXml.append(" <Quantity>104.6860</Quantity>");
240 tradeXml.append(" </FxAsianOptionData>");
241 tradeXml.append(" </Trade>");
242 tradeXml.append("</Portfolio>");
243
244 // Load portfolio from XML string
245 Portfolio portfolio;
246 portfolio.fromXMLString(tradeXml);
247
248 // Extract FxAsianOption trade from portfolio
249 QuantLib::ext::shared_ptr<Trade> trade = portfolio.trades().begin()->second;
250 QuantLib::ext::shared_ptr<FxAsianOption> option = QuantLib::ext::dynamic_pointer_cast<ore::data::FxAsianOption>(trade);
251 BOOST_CHECK(option != nullptr);
252
253 // Check fields after checking that the cast was successful
254 BOOST_CHECK(option);
255 BOOST_CHECK_EQUAL(option->tradeType(), "FxAsianOption");
256 BOOST_CHECK_EQUAL(option->id(), "FxAsianOption_USDJPY");
257 // BOOST_CHECK_EQUAL(option->asset(), "USD"); // only available after build
258 BOOST_CHECK_EQUAL(option->quantity(), 104.6860);
259 BOOST_CHECK_EQUAL(option->strike().value(), 104.6860);
260 BOOST_CHECK_EQUAL(option->indexName(), "FX-ECB-USD-JPY");
261 BOOST_CHECK_EQUAL(option->option().longShort(), "Long");
262 BOOST_CHECK_EQUAL(option->option().callPut(), "Call");
263 BOOST_CHECK_EQUAL(option->option().style(), "European");
264 BOOST_CHECK_EQUAL(option->option().exerciseDates().size(), 1);
265 BOOST_CHECK_EQUAL(option->option().exerciseDates()[0], "2021-02-26");
266 BOOST_CHECK(option->observationDates().hasData());
267
268 BOOST_CHECK_EQUAL(option->option().payoffType(), "Asian");
269 BOOST_CHECK_EQUAL(option->option().payoffType2(), "Arithmetic");
270 }
271
272 BOOST_AUTO_TEST_SUITE_END()
273
274 BOOST_AUTO_TEST_SUITE_END()
Asian option representation.
Serializable object holding generic trade data, reporting dimensions.
Definition: envelope.hpp:51
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 portfolio.
Definition: portfolio.hpp:43
const std::map< std::string, QuantLib::ext::shared_ptr< Trade > > & trades() const
Return the map tradeId -> trade.
Definition: portfolio.cpp:162
Serializable object holding premium data.
Definition: premiumdata.hpp:37
Serializable schedule data.
Definition: schedule.hpp:202
Serializable object holding schedule Dates data.
Definition: schedule.hpp:110
void fromXMLString(const std::string &xml)
Parse from XML string.
Definition: xmlutils.cpp:162
Engine builder for fx Asian options.
An implementation of the Market class that stores the required objects in maps.
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
Portfolio class.
BOOST_AUTO_TEST_CASE(testFxAsianOptionTradeBuilding)
string conversion utilities