19#include <boost/test/unit_test.hpp>
20#include <oret/toplevelfixture.hpp>
22#include <boost/algorithm/string/replace.hpp>
23#include <boost/make_shared.hpp>
25#include <ql/currencies/america.hpp>
26#include <ql/instruments/vanillaoption.hpp>
27#include <ql/math/interpolations/linearinterpolation.hpp>
28#include <ql/pricingengines/blackformula.hpp>
29#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
30#include <ql/termstructures/yield/flatforward.hpp>
40using namespace boost::unit_test_framework;
41using namespace boost::algorithm;
49Real testTolerance = 1e-10;
53Real cachedCallPrice = 5711.6321329012244;
54Real cachedPutPrice = 4919.5922659018906;
60 asof_ = Date(19, Feb, 2018);
61 Actual365Fixed dayCounter;
64 Handle<YieldTermStructure> discount(QuantLib::ext::make_shared<FlatForward>(asof_, 0.01, dayCounter));
68 vector<Date> dates = {
asof_, Date(19, Feb, 2019)};
69 vector<Real> prices = {1346.0, 1348.0};
70 Handle<PriceTermStructure> priceCurve(
72 Handle<CommodityIndex> commIdx(QuantLib::ext::make_shared<CommoditySpotIndex>(
"GOLD_USD", NullCalendar(), priceCurve));
76 vector<Date> volatilityDates = {Date(19, Feb, 2019), Date(19, Feb, 2020)};
77 vector<Real> volatilities = {0.10, 0.11};
78 Handle<BlackVolTermStructure> volatility(
79 QuantLib::ext::make_shared<BlackVarianceCurve>(asof_, volatilityDates, volatilities, dayCounter));
92 vector<string> expiry;
95 QuantLib::ext::shared_ptr<Market> market;
96 QuantLib::ext::shared_ptr<EngineFactory> engineFactory;
99 : commodityName(
"GOLD_USD"), currency(
"USD"), quantity(100), strike(1340.0,
"USD"), payOffAtExpiry(false),
100 expiry(1,
"2019-02-19"), expiryDate(19, Feb, 2019) {
103 market = QuantLib::ext::make_shared<TestMarket>();
104 QuantLib::ext::shared_ptr<EngineData> engineData = QuantLib::ext::make_shared<EngineData>();
105 engineData->model(
"CommodityOption") =
"BlackScholes";
106 engineData->engine(
"CommodityOption") =
"AnalyticEuropeanEngine";
107 engineFactory = QuantLib::ext::make_shared<EngineFactory>(engineData, market);
110 Settings::instance().evaluationDate() = market->asofDate();
116BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
118BOOST_AUTO_TEST_SUITE(CommodityOptionTests)
122 BOOST_TEST_MESSAGE(
"Testing commodity option trade building");
128 OptionData optionData(
"Long",
"Call",
"European", td.payOffAtExpiry, td.expiry);
129 QuantLib::ext::shared_ptr<CommodityOption> option = QuantLib::ext::make_shared<CommodityOption>(
130 td.envelope, optionData, td.commodityName, td.currency, td.quantity, td.strike);
131 BOOST_CHECK_NO_THROW(option->build(td.engineFactory));
134 QuantLib::ext::shared_ptr<Instrument> qlInstrument = option->instrument()->qlInstrument();
136 QuantLib::ext::shared_ptr<VanillaOption> vanillaOption = QuantLib::ext::dynamic_pointer_cast<VanillaOption>(qlInstrument);
137 BOOST_CHECK(vanillaOption);
138 BOOST_CHECK_EQUAL(vanillaOption->exercise()->type(), Exercise::Type::European);
139 BOOST_CHECK_EQUAL(vanillaOption->exercise()->dates().size(), 1);
140 BOOST_CHECK_EQUAL(vanillaOption->exercise()->dates()[0], td.expiryDate);
142 QuantLib::ext::shared_ptr<TypePayoff> payoff = QuantLib::ext::dynamic_pointer_cast<TypePayoff>(vanillaOption->payoff());
144 BOOST_CHECK_EQUAL(payoff->optionType(), Option::Type::Call);
149 Real forwardPrice = td.market->commodityPriceCurve(td.commodityName)->price(td.expiryDate);
150 DiscountFactor discount = td.market->discountCurve(td.currency)->discount(td.expiryDate);
151 Real
variance = td.market->commodityVolatility(td.commodityName)->blackVariance(td.expiryDate, td.strike.value());
153 td.quantity * blackFormula(Option::Type::Call, td.strike.value(), forwardPrice,
sqrt(
variance), discount);
154 BOOST_CHECK_CLOSE(expectedPrice, cachedCallPrice, testTolerance);
157 BOOST_CHECK_CLOSE(option->instrument()->NPV(), expectedPrice, testTolerance);
162 BOOST_TEST_MESSAGE(
"Testing parsing of commodity option trade from XML");
169 tradeXml.append(
"<Portfolio>");
170 tradeXml.append(
" <Trade id=\"CommodityOption_Gold\">");
171 tradeXml.append(
" <TradeType>CommodityOption</TradeType>");
172 tradeXml.append(
" <Envelope>");
173 tradeXml.append(
" <CounterParty>CPTY_A</CounterParty>");
174 tradeXml.append(
" <NettingSetId>CPTY_A</NettingSetId>");
175 tradeXml.append(
" <AdditionalFields/>");
176 tradeXml.append(
" </Envelope>");
177 tradeXml.append(
" <CommodityOptionData>");
178 tradeXml.append(
" <OptionData>");
179 tradeXml.append(
" <LongShort>Long</LongShort>");
180 tradeXml.append(
" <OptionType>Call</OptionType>");
181 tradeXml.append(
" <Style>European</Style>");
182 tradeXml.append(
" <Settlement>Cash</Settlement>");
183 tradeXml.append(
" <PayOffAtExpiry>false</PayOffAtExpiry>");
184 tradeXml.append(
" <ExerciseDates>");
185 tradeXml.append(
" <ExerciseDate>2019-02-19</ExerciseDate>");
186 tradeXml.append(
" </ExerciseDates>");
187 tradeXml.append(
" </OptionData>");
188 tradeXml.append(
" <Name>GOLD_USD</Name>");
189 tradeXml.append(
" <Currency>USD</Currency>");
190 tradeXml.append(
" <Strike>1340</Strike>");
191 tradeXml.append(
" <Quantity>100</Quantity>");
192 tradeXml.append(
" </CommodityOptionData>");
193 tradeXml.append(
" </Trade>");
194 tradeXml.append(
"</Portfolio>");
201 QuantLib::ext::shared_ptr<Trade> trade = portfolio.
trades().begin()->second;
202 QuantLib::ext::shared_ptr<CommodityOption> option = QuantLib::ext::dynamic_pointer_cast<ore::data::CommodityOption>(trade);
206 BOOST_CHECK_EQUAL(option->tradeType(),
"CommodityOption");
207 BOOST_CHECK_EQUAL(option->id(),
"CommodityOption_Gold");
208 BOOST_CHECK_EQUAL(option->asset(),
"GOLD_USD");
209 BOOST_CHECK_EQUAL(option->currency(),
"USD");
210 BOOST_CHECK_CLOSE(option->strike().value(), 1340, testTolerance);
211 BOOST_CHECK_CLOSE(option->quantity(), 100, testTolerance);
212 BOOST_CHECK_EQUAL(option->option().longShort(),
"Long");
213 BOOST_CHECK_EQUAL(option->option().callPut(),
"Call");
214 BOOST_CHECK_EQUAL(option->option().style(),
"European");
215 BOOST_CHECK_EQUAL(option->option().exerciseDates().size(), 1);
216 BOOST_CHECK_EQUAL(option->option().exerciseDates()[0],
"2019-02-19");
219 BOOST_CHECK_NO_THROW(option->build(td.engineFactory));
220 BOOST_CHECK_CLOSE(option->instrument()->NPV(), cachedCallPrice, testTolerance);
223 replace_all(tradeXml,
">Long<",
">Short<");
226 trade = portfolio.
trades().begin()->second;
227 BOOST_CHECK_NO_THROW(trade->build(td.engineFactory));
228 BOOST_CHECK_CLOSE(trade->instrument()->NPV(), -cachedCallPrice, testTolerance);
231 replace_all(tradeXml,
">Call<",
">Put<");
234 trade = portfolio.
trades().begin()->second;
235 BOOST_CHECK_NO_THROW(trade->build(td.engineFactory));
236 BOOST_CHECK_CLOSE(trade->instrument()->NPV(), -cachedPutPrice, testTolerance);
239 replace_all(tradeXml,
">Short<",
">Long<");
242 trade = portfolio.
trades().begin()->second;
243 BOOST_CHECK_NO_THROW(trade->build(td.engineFactory));
244 BOOST_CHECK_CLOSE(trade->instrument()->NPV(), cachedPutPrice, testTolerance);
249 BOOST_TEST_MESSAGE(
"Testing commodity option prices");
255 QuantLib::ext::shared_ptr<CommodityOption> option;
258 OptionData optionData(
"Long",
"Call",
"European", td.payOffAtExpiry, td.expiry);
259 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency,
260 td.quantity, td.strike);
261 option->build(td.engineFactory);
262 BOOST_CHECK_CLOSE(option->instrument()->NPV(), cachedCallPrice, testTolerance);
265 optionData =
OptionData(
"Short",
"Call",
"European", td.payOffAtExpiry, td.expiry);
266 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency,
267 td.quantity, td.strike);
268 option->build(td.engineFactory);
269 BOOST_CHECK_CLOSE(option->instrument()->NPV(), -cachedCallPrice, testTolerance);
272 optionData =
OptionData(
"Long",
"Put",
"European", td.payOffAtExpiry, td.expiry);
273 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency,
274 td.quantity, td.strike);
275 option->build(td.engineFactory);
276 BOOST_CHECK_CLOSE(option->instrument()->NPV(), cachedPutPrice, testTolerance);
279 optionData =
OptionData(
"Short",
"Put",
"European", td.payOffAtExpiry, td.expiry);
280 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency, td.quantity,
282 option->build(td.engineFactory);
283 BOOST_CHECK_CLOSE(option->instrument()->NPV(), -cachedPutPrice, testTolerance);
288 BOOST_TEST_MESSAGE(
"Testing commodity option exceptions during building");
294 QuantLib::ext::shared_ptr<CommodityOption> option;
297 OptionData optionData(
"Long",
"Call",
"European", td.payOffAtExpiry, td.expiry);
298 TradeStrike ts(TradeStrike::Type::Price, -td.strike.value());
299 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency, td.quantity, ts);
300 BOOST_CHECK_THROW(option->build(td.engineFactory), Error);
304 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData,
"GOLD_USD_MISSING", td.currency,
305 td.quantity, td.strike);
306 BOOST_CHECK_THROW(option->build(td.engineFactory), Error);
309 optionData =
OptionData(
"Long",
"Call",
"American", td.payOffAtExpiry, td.expiry);
310 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency, td.quantity,
312 BOOST_CHECK_THROW(option->build(td.engineFactory), Error);
315 vector<string> extraExpiries = td.expiry;
316 extraExpiries.push_back(
"2019-08-19");
317 optionData =
OptionData(
"Long",
"Call",
"European", td.payOffAtExpiry, extraExpiries);
318 option = QuantLib::ext::make_shared<CommodityOption>(td.envelope, optionData, td.commodityName, td.currency,
319 td.quantity, td.strike);
320 BOOST_CHECK_THROW(option->build(td.engineFactory), Error);
325 BOOST_TEST_MESSAGE(
"Testing commodity option premium works");
332 Date premiumDate(21, Feb, 2018);
335 OptionData optionData(
"Long",
"Call",
"European", td.payOffAtExpiry, td.expiry,
"Cash",
"",
336 PremiumData{premium, td.currency, Date(21, Feb, 2018)});
337 QuantLib::ext::shared_ptr<CommodityOption> option = QuantLib::ext::make_shared<CommodityOption>(
338 td.envelope, optionData, td.commodityName, td.currency, td.quantity, td.strike);
341 BOOST_CHECK_NO_THROW(option->build(td.engineFactory));
344 DiscountFactor premiumDiscount = td.market->discountCurve(td.currency)->discount(premiumDate);
345 BOOST_CHECK_CLOSE(option->instrument()->NPV(), cachedCallPrice - premiumDiscount * premium, testTolerance);
348BOOST_AUTO_TEST_SUITE_END()
350BOOST_AUTO_TEST_SUITE_END()
Engine builder for commodity options.
Serializable object holding generic trade data, reporting dimensions.
static const string defaultConfiguration
Default configuration label.
map< tuple< string, YieldCurveType, string >, Handle< YieldTermStructure > > yieldCurves_
map< pair< string, string >, QuantLib::Handle< QuantExt::CommodityIndex > > commodityIndices_
map< pair< string, string >, QuantLib::Handle< QuantLib::BlackVolTermStructure > > commodityVols_
Serializable object holding option data.
const std::map< std::string, QuantLib::ext::shared_ptr< Trade > > & trades() const
Return the map tradeId -> trade.
void clear()
Clear the portfolio.
Serializable object holding premium data.
void fromXMLString(const std::string &xml)
Parse from XML string.
Commodity option representation.
An implementation of the Market class that stores the required objects in maps.
RandomVariable sqrt(RandomVariable x)
RandomVariable variance(const RandomVariable &r)
BOOST_AUTO_TEST_CASE(testCommodityOptionTradeBuilding)