1180 {
1181 BOOST_TEST_MESSAGE("Testing autocallable...");
1182
1183 Date ref(7, May, 2019);
1184 Settings::instance().evaluationDate() = ref;
1185
1187 "NUMBER StrikePrice, KnockInPrice, Value;\n"
1188 "NUMBER terminated, knockedIn, u, v;\n"
1189 "FOR u IN (1, SIZE(Underlying), 1) DO\n"
1190 " StrikePrice = StrikePrice + Underlying[u](StrikeDate);\n"
1191 "END;\n"
1192 "StrikePrice = StrikePrice / SIZE(Underlying);\n"
1193 "KnockInPrice = KnockInRatio * StrikePrice;\n"
1194 "FOR v IN (1, SIZE(Valuation), 1) DO\n"
1195 " Value = 0;\n"
1196 " FOR u IN (1, SIZE(Underlying), 1) DO\n"
1197 " Value = Value + Underlying[u](Valuation[v]);\n"
1198 " END;\n"
1199 " Value = Value / SIZE(Underlying);\n"
1200 " IF Value < KnockInPrice THEN\n"
1201 " knockedIn = 1;\n"
1202 " END;\n"
1203 " IF v == SIZE(Valuation) THEN\n"
1204 " IF knockedIn == 1 AND terminated == 0 THEN\n"
1205 " Option = PAY(Notional * ( 1 - Value / StrikePrice), Valuation[v], Settlement[v], PayCcy);\n"
1206 " END;\n"
1207 " ELSE\n"
1208 " IF v > 1 AND terminated == 0 THEN\n"
1209 " IF Value > StrikePrice THEN\n"
1210 " Option = PAY(Notional * v * 0.06, Valuation[v], Settlement[v], PayCcy);\n"
1211 " terminated = 1;\n"
1212 " END;\n"
1213 " END;\n"
1214 " END;\n"
1215 "END;\n";
1216
1218 BOOST_REQUIRE(parser.success());
1219 BOOST_TEST_MESSAGE(
"Parsing successful, AST:\n" <<
to_string(parser.ast()));
1220
1221 Real s0 = 100.0;
1222 Real vol = 0.18;
1223 Real rate = 0.02;
1224 Real notional = 1000.0;
1225 Real strike = 100.0;
1226 Real knockInRatio = 0.7;
1227
1228 constexpr Size nPaths = 10000;
1229
1230 Schedule observationSchedule(Date(9, May, 2019), Date(9, May, 2020), 1 * Months, NullCalendar(), Unadjusted,
1231 Unadjusted, DateGeneration::Forward, false);
1232 std::vector<ValueType> observationDates, settlementDates;
1233 for (Size i = 1; i < observationSchedule.dates().
size(); ++i) {
1234 Date d = observationSchedule.date(i);
1235 observationDates.push_back(
EventVec{nPaths, d});
1236 settlementDates.push_back(
EventVec{nPaths, d + 5});
1237 }
1238 std::vector<Date> expectedSimDates = observationSchedule.dates();
1239 std::vector<std::string> indicesStr = {"EQ-1", "EQ-2", "EQ-3"};
1240 std::vector<ValueType> indices;
1241 for (auto const& i : indicesStr)
1242 indices.push_back(
IndexVec{nPaths, i});
1243
1244 auto context = QuantLib::ext::make_shared<Context>();
1247 context->scalars[
"StrikeDate"] =
EventVec{nPaths, observationSchedule.dates().front()};
1248 context->scalars[
"KnockInRatio"] =
RandomVariable(nPaths, knockInRatio);
1249 context->arrays["Underlying"] = indices;
1250 context->arrays["Valuation"] = observationDates;
1251 context->arrays["Settlement"] = settlementDates;
1252 context->scalars[
"PayCcy"] =
CurrencyVec{nPaths,
"USD"};
1254
1255 auto indexInfo = QuantLib::ext::make_shared<StaticAnalyser>(parser.ast(), context);
1256 BOOST_REQUIRE_NO_THROW(indexInfo->run());
1257 BOOST_REQUIRE_EQUAL(indexInfo->indexEvalDates().size(), 3);
1258 Size i = 0;
1259 for (auto const& k : indexInfo->indexEvalDates())
1260 BOOST_CHECK_EQUAL(k.first, indicesStr[i++]);
1261 for (auto const& ind : indicesStr) {
1262 Size i = 0;
1263 BOOST_CHECK_EQUAL(indexInfo->indexEvalDates().at(ind).size(), expectedSimDates.size());
1264 for (auto const& d : indexInfo->indexEvalDates().at(ind))
1265 BOOST_CHECK_EQUAL(d, expectedSimDates[i++]);
1266 }
1267 BOOST_REQUIRE_EQUAL(indexInfo->payObsDates().size(), 1);
1268 BOOST_CHECK_EQUAL(indexInfo->payObsDates().begin()->first, "USD");
1269 i = 0;
1270 BOOST_REQUIRE_EQUAL(indexInfo->payObsDates().begin()->second.size(), observationDates.size());
1271 for (auto const& d : indexInfo->payObsDates().begin()->second) {
1272 BOOST_CHECK_EQUAL(d, QuantLib::ext::get<EventVec>(observationDates[i++]).
value);
1273 }
1274 BOOST_REQUIRE_EQUAL(indexInfo->payPayDates().size(), 1);
1275 BOOST_CHECK_EQUAL(indexInfo->payPayDates().begin()->first, "USD");
1276 i = 0;
1277 BOOST_REQUIRE_EQUAL(indexInfo->payPayDates().begin()->second.size(), settlementDates.size());
1278 for (auto const& d : indexInfo->payPayDates().begin()->second) {
1279 BOOST_CHECK_EQUAL(d, QuantLib::ext::get<EventVec>(settlementDates[i++]).
value);
1280 }
1281
1282
1283 std::set<Date> simulationDates, payDates;
1284 for (auto const& s : indexInfo->indexEvalDates())
1285 simulationDates.insert(s.second.begin(), s.second.end());
1286 for (auto const& s : indexInfo->payObsDates())
1287 simulationDates.insert(s.second.begin(), s.second.end());
1288 simulationDates.insert(indexInfo->regressionDates().begin(), indexInfo->regressionDates().end());
1289 for (auto const& s : indexInfo->payPayDates())
1290 payDates.insert(s.second.begin(), s.second.end());
1291
1292 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(ref, rate, ActualActual(ActualActual::ISDA)));
1293 Handle<YieldTermStructure> yts0(QuantLib::ext::make_shared<FlatForward>(ref, 0.0, ActualActual(ActualActual::ISDA)));
1294 Handle<BlackVolTermStructure> volts(
1295 QuantLib::ext::make_shared<BlackConstantVol>(ref, NullCalendar(), vol, ActualActual(ActualActual::ISDA)));
1296 auto process1 = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
1297 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
1298 auto process2 = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
1299 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
1300 auto process3 = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
1301 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
1302 std::vector<QuantLib::ext::shared_ptr<QuantLib::StochasticProcess1D>> processes = {process1, process2, process3};
1303 std::vector<QuantLib::ext::shared_ptr<GeneralizedBlackScholesProcess>> processesBs = {process1, process2, process3};
1304 std::map<std::pair<std::string, std::string>, Handle<QuantExt::CorrelationTermStructure>> correlations;
1305 correlations[std::make_pair("EQ-1", "EQ-2")] = Handle<QuantExt::CorrelationTermStructure>(
1306 QuantLib::ext::make_shared<QuantExt::FlatCorrelation>(0, NullCalendar(), 0.5, ActualActual(ActualActual::ISDA)));
1307 correlations[std::make_pair("EQ-1", "EQ-3")] = Handle<QuantExt::CorrelationTermStructure>(
1308 QuantLib::ext::make_shared<QuantExt::FlatCorrelation>(0, NullCalendar(), 0.4, ActualActual(ActualActual::ISDA)));
1309 correlations[std::make_pair("EQ-2", "EQ-3")] = Handle<QuantExt::CorrelationTermStructure>(
1310 QuantLib::ext::make_shared<QuantExt::FlatCorrelation>(0, NullCalendar(), 0.6, ActualActual(ActualActual::ISDA)));
1311 cpu_timer timer;
1314 auto model = QuantLib::ext::make_shared<BlackScholes>(
1315 nPaths, std::vector<std::string>(1, "USD"), std::vector<Handle<YieldTermStructure>>(1, yts),
1316 std::vector<Handle<Quote>>(), std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<InterestRateIndex>>>(),
1317 std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<ZeroInflationIndex>>>(), indicesStr,
1318 std::vector<std::string>(3, "USD"),
1320 simulationDates);
1322 BOOST_REQUIRE_NO_THROW(engine.run());
1324 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars[
"Option"]);
1325 BOOST_REQUIRE(rv.
size() == nPaths);
1327 timer.stop();
1328 BOOST_TEST_MESSAGE("option value estimation " << avg << " (timing " << timer.format(default_places, "%w") << "s)");
1329 BOOST_TEST_MESSAGE(*context);
1330
1331
1332 std::vector<Real> times;
1333 for (auto const& d : expectedSimDates)
1334 times.push_back(yts->timeFromReference(d));
1335 TimeGrid grid(times.begin(), times.end(), 1);
1336 std::vector<Size> positionInTimeGrid(times.size());
1337 for (Size i = 0; i < times.size(); ++i)
1338 positionInTimeGrid[i] = grid.index(times[i]);
1339
1340 for (Size i = 0; i < times.size(); ++i)
1341 BOOST_TEST_MESSAGE("time point #" << i << ": " << times[i] << ", position in grid " << positionInTimeGrid[i]);
1342 for (Size i = 0; i < grid.size(); ++i)
1343 BOOST_TEST_MESSAGE("grid point #" << i << ": " << grid[i]);
1344
1345 Matrix correlation{{1.0, 0.5, 0.4}, {0.5, 1.0, 0.6}, {0.4, 0.6, 1.0}};
1346 auto process = QuantLib::ext::make_shared<StochasticProcessArray>(processes, correlation);
1348 double avg2 = 0.0;
1349 timer.start();
1350 constexpr Size nUnd = 3;
1351 const Size nObs = observationDates.size();
1352 for (Size path = 0; path < nPaths; ++path) {
1353 MultiPath p = pg->next().value;
1354
1355 Real Option = 0.0;
1356 Real StrikePrice = 0.0, KnockInPrice = 0.0, Value = 0.0;
1357 Size terminated = 0, knockedIn = 0;
1358 for (Size u = 0; u < nUnd; ++u)
1359 StrikePrice += p[u][positionInTimeGrid[1 - 1]];
1360 StrikePrice /= nUnd;
1361 KnockInPrice = knockInRatio * StrikePrice;
1362 for (Size v = 0; v < nObs; ++v) {
1363 Value = 0;
1364 for (Size u = 0; u < nUnd; ++u) {
1365 Value += p[u][positionInTimeGrid[v + 2 - 1]];
1366 }
1367 Value /= nUnd;
1368 if (Value < KnockInPrice && !
close_enough(Value, KnockInPrice))
1369 knockedIn = 1;
1370 if (v == nObs - 1) {
1371 if (knockedIn == 1 && terminated == 0) {
1372 Option = notional * (1 - Value / StrikePrice) *
1373 yts->discount(QuantLib::ext::get<EventVec>(settlementDates[v]).value);
1374 }
1375 } else {
1376 if (v > 0 && terminated == 0) {
1377 if (Value > StrikePrice && !
close_enough(Value, StrikePrice)) {
1378 Option =
1379 notional * (v + 1) * 0.06 * yts->discount(QuantLib::ext::get<EventVec>(settlementDates[v]).value);
1380 terminated = 1;
1381 }
1382 }
1383 }
1384 }
1385
1386 avg2 += Option;
1387 }
1388 avg2 /= nPaths;
1389 timer.stop();
1390 BOOST_TEST_MESSAGE("result with hardcoded script " << avg2 << "(timing " << timer.format(default_places, "%w")
1391 << "s)");
1392
1393 BOOST_CHECK_CLOSE(avg, avg2, 1.0);
1394}
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Size size(const ValueType &v)