Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
scriptengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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// clang-format off
20#include <boost/test/unit_test.hpp>
21#include <boost/test/data/test_case.hpp>
22// clang-format on
23#include <boost/timer/timer.hpp>
24
31
32#include <oret/toplevelfixture.hpp>
33
35
39
40#include <ql/indexes/ibor/eonia.hpp>
41#include <ql/instruments/vanillaoption.hpp>
42#include <ql/pricingengines/blackformula.hpp>
43#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
44#include <ql/processes/stochasticprocessarray.hpp>
45#include <ql/quotes/simplequote.hpp>
46#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
47#include <ql/termstructures/yield/flatforward.hpp>
48#include <ql/time/calendars/nullcalendar.hpp>
49#include <ql/time/daycounters/actualactual.hpp>
50
51#include <iomanip>
52#include <iostream>
53
54using namespace ore::data;
55using namespace QuantExt;
56using boost::timer::cpu_timer;
57using boost::timer::default_places;
58
59BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)
60
61BOOST_AUTO_TEST_SUITE(ScriptEngineTest)
62
63BOOST_AUTO_TEST_CASE(testSimpleScript) {
64 BOOST_TEST_MESSAGE("Testing simple script...");
65 std::string script = "NUMBER x,i; FOR i IN (1,100,1) DO x = x + i; END;";
66 ScriptParser parser(script);
67 BOOST_REQUIRE(parser.success());
68 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
69
70 auto context = QuantLib::ext::make_shared<Context>();
71 ScriptEngine engine(parser.ast(), context);
72 BOOST_REQUIRE_NO_THROW(engine.run());
73 BOOST_TEST_MESSAGE("Script Engine successfully run, context is:\n" << *context);
74
75 BOOST_CHECK(deterministic(context->scalars["i"]));
76 BOOST_CHECK(deterministic(context->scalars["x"]));
77
78 BOOST_CHECK(equal(context->scalars["i"], ValueType(RandomVariable(1, 100.0))).at(0));
79 BOOST_CHECK(equal(context->scalars["x"], ValueType(RandomVariable(1, 100.0 / 2.0 * 101.0l))).at(0));
80}
81
82namespace {
83// helper for testFunctions
84RandomVariable executeScript(const std::string& script, const QuantLib::ext::shared_ptr<Context> initialContext) {
85 ScriptParser parser(script);
86 BOOST_REQUIRE(parser.success());
87 auto context = QuantLib::ext::make_shared<Context>(*initialContext);
88 ScriptEngine engine(parser.ast(), context, QuantLib::ext::make_shared<DummyModel>(1));
89 BOOST_REQUIRE_NO_THROW(engine.run());
90 BOOST_REQUIRE(context->scalars.find("result") != context->scalars.end());
91 BOOST_REQUIRE(context->scalars.at("result").which() == ValueTypeWhich::Number);
92 return QuantLib::ext::get<RandomVariable>(context->scalars.at("result"));
93}
94} // namespace
95
96BOOST_AUTO_TEST_CASE(testFunctions) {
97 BOOST_TEST_MESSAGE("Testing functions...");
98
99 auto c = QuantLib::ext::make_shared<Context>();
100 RandomVariable x(1, 2.0);
101 RandomVariable y(1, -2.0);
102 RandomVariable result(1, 0.0);
103 c->scalars["x"] = x;
104 c->scalars["y"] = y;
105 c->scalars["omega"] = RandomVariable(1, -1.0);
106 c->scalars["ref"] = EventVec{1, Date(6, Jun, 2019)};
107 c->scalars["expiry"] = EventVec{1, Date(6, Jun, 2022)};
108 c->scalars["strike"] = RandomVariable(1, 98.0);
109 c->scalars["forward"] = RandomVariable(1, 100.0);
110 c->scalars["vol"] = RandomVariable(1, 0.2);
111 c->scalars["result"] = result;
112
113 BOOST_CHECK(close_enough_all(executeScript("result=x+y;", c), x + y));
114 BOOST_CHECK(close_enough_all(executeScript("result=x-y;", c), x - y));
115 BOOST_CHECK(close_enough_all(executeScript("result=x*y;", c), x * y));
116 BOOST_CHECK(close_enough_all(executeScript("result=x/y;", c), x / y));
117
118 BOOST_CHECK(close_enough_all(executeScript("result=-x;", c), -x));
119 BOOST_CHECK(close_enough_all(executeScript("result=abs(y);", c), abs(y)));
120 BOOST_CHECK(close_enough_all(executeScript("result=exp(x);", c), exp(x)));
121 BOOST_CHECK(close_enough_all(executeScript("result=ln(x);", c), log(x)));
122 BOOST_CHECK(close_enough_all(executeScript("result=sqrt(x);", c), sqrt(x)));
123 BOOST_CHECK(close_enough_all(executeScript("result=normalCdf(x);", c), normalCdf(x)));
124 BOOST_CHECK(close_enough_all(executeScript("result=normalPdf(x);", c), normalPdf(x)));
125
126 BOOST_CHECK(close_enough_all(executeScript("result=max(x,y);", c), max(x, y)));
127 BOOST_CHECK(close_enough_all(executeScript("result=min(x,y);", c), min(x, y)));
128 BOOST_CHECK(close_enough_all(executeScript("result=pow(x,y);", c), pow(x, y)));
129
130 BOOST_CHECK(close_enough_all(executeScript("result=x+y-y+x/y*y-x;", c), x + y - y + x / y * y - x));
131
132 BOOST_CHECK_CLOSE(
133 executeScript("result=black(omega,ref,expiry,strike,forward,vol);", c).at(0),
134 blackFormula(
135 Option::Put, 98.0, 100.0,
136 0.2 * std::sqrt(ActualActual(ActualActual::ISDA).yearFraction(Date(6, June, 2019), Date(6, June, 2022)))),
137 1E-10);
138}
139
140BOOST_AUTO_TEST_CASE(testDaycounterFunctions) {
141 BOOST_TEST_MESSAGE("Testing daycounter functions...");
142
143 Date d1(15, Sep, 2019), d2(8, Jan, 2033);
144 Actual365Fixed dc;
145
146 auto c = QuantLib::ext::make_shared<Context>();
147 EventVec date1{1, d1};
148 EventVec date2{1, d2};
149 DaycounterVec daycounter{1, "A365F"};
150 RandomVariable result(1, 0.0);
151 c->scalars["date1"] = date1;
152 c->scalars["date2"] = date2;
153 c->scalars["daycounter"] = daycounter;
154 c->scalars["result"] = result;
155
156 BOOST_CHECK_CLOSE(executeScript("result=dcf(daycounter, date1, date2);", c).at(0), dc.yearFraction(d1, d2), 1E-10);
157 BOOST_CHECK_CLOSE(executeScript("result=days(daycounter, date1, date2);", c).at(0), dc.dayCount(d1, d2), 1E-12);
158}
159
160BOOST_AUTO_TEST_CASE(testSortFunction) {
161 BOOST_TEST_MESSAGE("Testing sort function...");
162
163 constexpr Real tol = 1E-14;
164
165 RandomVariable x1(2), x2(2), x3(2), x4(2);
166 x1.set(0,3.0);
167 x1.set(1,1.0);
168 x2.set(0,4.0);
169 x2.set(1,2.0);
170 x3.set(0,2.0);
171 x3.set(1,4.0);
172 x4.set(0,1.0);
173 x4.set(1,3.0);
174
175 std::vector<RandomVariable> x{x1, x2, x3, x4};
176 std::vector<ValueType> xv;
177 for (auto const& c : x)
178 xv.push_back(c);
179
180 std::vector<ValueType> y(4, RandomVariable(2)), i(4, RandomVariable(2));
181
182 auto c0 = QuantLib::ext::make_shared<Context>();
183 c0->arrays["x"] = xv;
184 c0->arrays["y"] = y;
185 c0->arrays["i"] = i;
186
187 // sort x, write result back to x
188 auto c = QuantLib::ext::make_shared<Context>(*c0);
189 ScriptEngine engine(ScriptParser("SORT (x);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
190 BOOST_REQUIRE_NO_THROW(engine.run());
191 std::vector<ValueType> result = c->arrays.at("x");
192 BOOST_REQUIRE(result.size() == x.size()); // check array size
193 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
194 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
195 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 1.0, tol); // check path #0
196 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 2.0, tol);
197 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 3.0, tol);
198 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 4.0, tol);
199 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
200 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
201 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
202 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
203
204 // sort x, but store result in y
205 c = QuantLib::ext::make_shared<Context>(*c0);
206 ScriptEngine engine2(ScriptParser("SORT ( x, y );").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
207 BOOST_REQUIRE_NO_THROW(engine2.run());
208 result = c->arrays.at("x");
209 BOOST_REQUIRE(result.size() == x.size()); // check array size
210 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
211 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
212 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), x.at(0).at(0), tol); // check path #0
213 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), x.at(1).at(0), tol);
214 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), x.at(2).at(0), tol);
215 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), x.at(3).at(0), tol);
216 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), x.at(0).at(1), tol); // check path #1
217 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), x.at(1).at(1), tol);
218 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), x.at(2).at(1), tol);
219 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), x.at(3).at(1), tol);
220 result = c->arrays.at("y");
221 BOOST_REQUIRE(result.size() == x.size()); // check array size
222 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
223 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
224 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 1.0, tol); // check path #0
225 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 2.0, tol);
226 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 3.0, tol);
227 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 4.0, tol);
228 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
229 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
230 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
231 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
232
233 // sort x, store result in y and index permutation in i
234 c = QuantLib::ext::make_shared<Context>(*c0);
235 ScriptEngine engine3(ScriptParser("SORT ( x, y, i );").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
236 BOOST_REQUIRE_NO_THROW(engine3.run());
237 result = c->arrays.at("x");
238 BOOST_REQUIRE(result.size() == x.size()); // check array size
239 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
240 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
241 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), x.at(0).at(0), tol); // check path #0
242 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), x.at(1).at(0), tol);
243 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), x.at(2).at(0), tol);
244 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), x.at(3).at(0), tol);
245 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), x.at(0).at(1), tol); // check path #1
246 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), x.at(1).at(1), tol);
247 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), x.at(2).at(1), tol);
248 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), x.at(3).at(1), tol);
249 result = c->arrays.at("y");
250 BOOST_REQUIRE(result.size() == x.size()); // check array size
251 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
252 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
253 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 1.0, tol); // check path #0
254 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 2.0, tol);
255 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 3.0, tol);
256 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 4.0, tol);
257 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
258 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
259 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
260 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
261 result = c->arrays.at("i");
262 BOOST_REQUIRE(result.size() == x.size()); // check array size
263 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
264 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
265 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 4.0, tol); // check path #0
266 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 3.0, tol);
267 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 1.0, tol);
268 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 2.0, tol);
269 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
270 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
271 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 4.0, tol);
272 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 3.0, tol);
273
274 // check illegal call with y having a different size from x
275 c = QuantLib::ext::make_shared<Context>(*c0);
276 c->arrays["y"] = std::vector<ValueType>(3);
277 ScriptEngine engine4(ScriptParser("SORT(x,y);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
278 BOOST_REQUIRE_THROW(engine4.run(), std::exception);
279
280 // check illegal call with i having a different size from x (but y has the correct size)
281 c = QuantLib::ext::make_shared<Context>(*c0);
282 c->arrays["i"] = std::vector<ValueType>(3);
283 ScriptEngine engine5(ScriptParser("SORT(x,y,i);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
284 BOOST_REQUIRE_THROW(engine5.run(), std::exception);
285}
286
287BOOST_AUTO_TEST_CASE(testPermuteFunction) {
288 BOOST_TEST_MESSAGE("Testing permute function...");
289
290 constexpr Real tol = 1E-14;
291
292 RandomVariable x1(2), x2(2), x3(2), x4(2);
293 x1.set(0,3.0);
294 x1.set(1,1.0);
295 x2.set(0,4.0);
296 x2.set(1,2.0);
297 x3.set(0,2.0);
298 x3.set(1,4.0);
299 x4.set(0,1.0);
300 x4.set(1,3.0);
301
302 RandomVariable p1(2), p2(2), p3(2), p4(2);
303 p1.set(0,4.0);
304 p1.set(1,1.0);
305 p2.set(0,3.0);
306 p2.set(1,2.0);
307 p3.set(0,1.0);
308 p3.set(1,4.0);
309 p4.set(0,2.0);
310 p4.set(1,3.0);
311
312 std::vector<RandomVariable> x{x1, x2, x3, x4};
313 std::vector<RandomVariable> p{p1, p2, p3, p4};
314 std::vector<ValueType> xv, pv;
315 for (auto const& c : x)
316 xv.push_back(c);
317 for (auto const& c : p)
318 pv.push_back(c);
319
320 std::vector<ValueType> yv(4, RandomVariable(2));
321
322 auto c0 = QuantLib::ext::make_shared<Context>();
323 c0->arrays["x"] = xv;
324 c0->arrays["y"] = yv;
325 c0->arrays["p"] = pv;
326
327 // permute x, write result back to x
328 auto c = QuantLib::ext::make_shared<Context>(*c0);
329 ScriptEngine engine(ScriptParser("PERMUTE (x,p);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
330 BOOST_REQUIRE_NO_THROW(engine.run());
331 std::vector<ValueType> result = c->arrays.at("x");
332 BOOST_REQUIRE(result.size() == x.size()); // check array size
333 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
334 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
335 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 1.0, tol); // check path #0
336 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 2.0, tol);
337 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 3.0, tol);
338 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 4.0, tol);
339 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
340 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
341 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
342 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
343
344 // permute x, but store result in y
345 c = QuantLib::ext::make_shared<Context>(*c0);
346 ScriptEngine engine2(ScriptParser("PERMUTE ( x, y, p);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
347 BOOST_REQUIRE_NO_THROW(engine2.run());
348 result = c->arrays.at("x");
349 BOOST_REQUIRE(result.size() == x.size()); // check array size
350 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
351 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
352 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), x.at(0).at(0), tol); // check path #0
353 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), x.at(1).at(0), tol);
354 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), x.at(2).at(0), tol);
355 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), x.at(3).at(0), tol);
356 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), x.at(0).at(1), tol); // check path #1
357 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), x.at(1).at(1), tol);
358 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), x.at(2).at(1), tol);
359 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), x.at(3).at(1), tol);
360 result = c->arrays.at("y");
361 BOOST_REQUIRE(result.size() == x.size()); // check array size
362 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
363 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
364 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), 1.0, tol); // check path #0
365 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), 2.0, tol);
366 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), 3.0, tol);
367 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), 4.0, tol);
368 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
369 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
370 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
371 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
372
373 // check illegal call with p having a different size from x
374 c = QuantLib::ext::make_shared<Context>(*c0);
375 c->arrays["p"] = std::vector<ValueType>(5);
376 ScriptEngine engine3(ScriptParser("PERMUTE(x,p);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
377 BOOST_REQUIRE_THROW(engine3.run(), std::exception);
378
379 // check illegal call with y having a different size from x, but p having the correct size
380 c = QuantLib::ext::make_shared<Context>(*c0);
381 c->arrays["y"] = std::vector<ValueType>(5);
382 ScriptEngine engine4(ScriptParser("PERMUTE(x,y,p);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
383 BOOST_REQUIRE_THROW(engine4.run(), std::exception);
384
385 // check illegal call with p having the correct size, but containing an illegal permutation index
386 c = QuantLib::ext::make_shared<Context>(*c0);
387 std::vector<ValueType> pv2 = pv;
388 QuantLib::ext::get<RandomVariable>(pv2[2]).set(1, 5.0);
389 c->arrays["p"] = pv2;
390 ScriptEngine engine5(ScriptParser("PERMUTE(x,p);").ast(), c, QuantLib::ext::make_shared<DummyModel>(2));
391 BOOST_REQUIRE_THROW(engine5.run(), std::exception);
392}
393
394BOOST_AUTO_TEST_CASE(testSortPermuteFunctionsWithFilter) {
395 BOOST_TEST_MESSAGE("Testing sort and permute functions with filter...");
396
397 constexpr Real tol = 1E-14;
398
399 RandomVariable x1(2), x2(2), x3(2), x4(2);
400 x1.set(0,3.0);
401 x1.set(1,1.0);
402 x2.set(0,4.0);
403 x2.set(1,2.0);
404 x3.set(0,2.0);
405 x3.set(1,4.0);
406 x4.set(0,1.0);
407 x4.set(1,3.0);
408
409 RandomVariable p1(2), p2(2), p3(2), p4(2);
410 p1.set(0,4.0);
411 p1.set(1,1.0);
412 p2.set(0,3.0);
413 p2.set(1,2.0);
414 p3.set(0,1.0);
415 p3.set(1,4.0);
416 p4.set(0,2.0);
417 p4.set(1,3.0);
418
419 std::vector<RandomVariable> x{x1, x2, x3, x4};
420 std::vector<RandomVariable> p{p1, p2, p3, p4};
421 std::vector<ValueType> xv, pv;
422 for (auto const& c : x)
423 xv.push_back(c);
424 for (auto const& c : p)
425 pv.push_back(c);
426
427 RandomVariable indicator(2);
428 indicator.set(0, 0.0);
429 indicator.set(1, 1.0);
430
431 auto c0 = QuantLib::ext::make_shared<Context>();
432 c0->arrays["x"] = xv;
433 c0->arrays["p"] = xv;
434 c0->scalars["indicator"] = indicator;
435
436 // sort x if y is positive, i.e. on path #1, but not on path #0
437 auto c = QuantLib::ext::make_shared<Context>(*c0);
438 ScriptEngine engine(ScriptParser("IF indicator > 0 THEN SORT (x); END;").ast(), c,
439 QuantLib::ext::make_shared<DummyModel>(2));
440 BOOST_REQUIRE_NO_THROW(engine.run());
441 std::vector<ValueType> result = c->arrays.at("x");
442 BOOST_REQUIRE(result.size() == x.size()); // check array size
443 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
444 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
445 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), x.at(0).at(0), tol); // check path #0
446 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), x.at(1).at(0), tol);
447 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), x.at(2).at(0), tol);
448 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), x.at(3).at(0), tol);
449 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
450 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
451 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
452 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
453
454 // permute x if y is positive, i.e. again on path #1, but not on path#0
455 c = QuantLib::ext::make_shared<Context>(*c0);
456 ScriptEngine engine2(ScriptParser("IF indicator > 0 THEN PERMUTE (x,p); END;").ast(), c,
457 QuantLib::ext::make_shared<DummyModel>(2));
458 BOOST_REQUIRE_NO_THROW(engine2.run());
459 result = c->arrays.at("x");
460 BOOST_REQUIRE(result.size() == x.size()); // check array size
461 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(0)).size() == x.at(0).size()); // check number of paths
462 BOOST_REQUIRE(QuantLib::ext::get<RandomVariable>(result.at(1)).size() == x.at(1).size());
463 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(0), x.at(0).at(0), tol); // check path #0
464 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(0), x.at(1).at(0), tol);
465 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(0), x.at(2).at(0), tol);
466 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(0), x.at(3).at(0), tol);
467 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(0)).at(1), 1.0, tol); // check path #1
468 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(1)).at(1), 2.0, tol);
469 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(2)).at(1), 3.0, tol);
470 BOOST_CHECK_CLOSE(QuantLib::ext::get<RandomVariable>(result.at(3)).at(1), 4.0, tol);
471}
472
473BOOST_AUTO_TEST_CASE(testHistoricFixingsFunction) {
474 BOOST_TEST_MESSAGE("Testing HISTFIXING() function...");
475
476 std::string script = "NUMBER hasFixing1, hasFixing2, hasFixing3;\n"
477 "hasFixing1 = HISTFIXING(Underlying, date1);\n"
478 "hasFixing2 = HISTFIXING(Underlying, date2);\n"
479 "hasFixing3 = HISTFIXING(Underlying, date3);\n";
480 ScriptParser parser(script);
481 BOOST_REQUIRE(parser.success());
482 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
483
484 constexpr Size nPaths = 50000;
485
486 auto context = QuantLib::ext::make_shared<Context>();
487 context->scalars["Underlying"] = IndexVec{nPaths, "EQ-SP5"};
488 context->scalars["date1"] = EventVec{nPaths, Date(7, May, 2019)};
489 context->scalars["date2"] = EventVec{nPaths, Date(8, May, 2019)};
490 context->scalars["date3"] = EventVec{nPaths, Date(9, May, 2019)};
491
492 class MyModel : public DummyModel {
494 const Date& referenceDate() const override {
495 static Date date(8, May, 2019);
496 return date;
497 }
498 };
499
500 auto model = QuantLib::ext::make_shared<MyModel>(nPaths);
501
502 EquityIndex2 ind("SP5", NullCalendar(), Currency());
503 ind.addFixing(Date(8, May, 2019), 100.0);
504 ind.addFixing(Date(9, May, 2019), 100.0);
505
506 ScriptEngine engine(parser.ast(), context, model);
507 BOOST_REQUIRE_NO_THROW(engine.run());
508 BOOST_REQUIRE(context->scalars["hasFixing1"].which() == ValueTypeWhich::Number);
509 BOOST_REQUIRE(context->scalars["hasFixing2"].which() == ValueTypeWhich::Number);
510 BOOST_REQUIRE(context->scalars["hasFixing3"].which() == ValueTypeWhich::Number);
511
512 RandomVariable rv1 = QuantLib::ext::get<RandomVariable>(context->scalars["hasFixing1"]);
513 RandomVariable rv2 = QuantLib::ext::get<RandomVariable>(context->scalars["hasFixing2"]);
514 RandomVariable rv3 = QuantLib::ext::get<RandomVariable>(context->scalars["hasFixing3"]);
515
516 BOOST_CHECK(rv1.deterministic());
517 BOOST_CHECK(rv2.deterministic());
518 BOOST_CHECK(rv3.deterministic());
519
520 constexpr Real tol = 1E-10;
521 BOOST_CHECK_CLOSE(rv1.at(0), 0.0, tol); // no historic fixing set
522 BOOST_CHECK_CLOSE(rv2.at(0), 1.0, tol); // have historic fixing on today
523 BOOST_CHECK_CLOSE(rv3.at(0), 0.0, tol); // have historic fixing, but date is > today
524}
525
526BOOST_AUTO_TEST_CASE(testDateIndexFunctionEq) {
527 BOOST_TEST_MESSAGE("Testing DATEINDEX(...,...,EQ) function");
528
529 std::string script = "NUMBER i;\n"
530 "i = DATEINDEX(d, a, EQ);";
531 ScriptParser parser(script);
532 BOOST_REQUIRE(parser.success());
533 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
534
535 constexpr Size nPaths = 10;
536 constexpr Real tol = 1E-10;
537
538 auto model = QuantLib::ext::make_shared<DummyModel>(nPaths);
539
540 std::vector<ValueType> dates;
541 dates.push_back(EventVec{nPaths, Date(7, May, 2019)});
542 dates.push_back(EventVec{nPaths, Date(10, June, 2020)});
543
544 // find date at index 1
545 auto context1 = QuantLib::ext::make_shared<Context>();
546 context1->arrays["a"] = dates;
547 context1->scalars["d"] = EventVec{nPaths, Date(7, May, 2019)};
548 ScriptEngine engine1(parser.ast(), context1, model);
549 BOOST_REQUIRE_NO_THROW(engine1.run());
550 BOOST_REQUIRE(context1->scalars["i"].which() == ValueTypeWhich::Number);
551 RandomVariable rv1 = QuantLib::ext::get<RandomVariable>(context1->scalars["i"]);
552 BOOST_CHECK(rv1.deterministic());
553 BOOST_CHECK_CLOSE(rv1.at(0), 1.0, tol);
554
555 // find date at index 2
556 auto context2 = QuantLib::ext::make_shared<Context>();
557 context2->arrays["a"] = dates;
558 context2->scalars["d"] = EventVec{nPaths, Date(10, June, 2020)};
559 ScriptEngine engine2(parser.ast(), context2, model);
560 BOOST_REQUIRE_NO_THROW(engine2.run());
561 BOOST_REQUIRE(context2->scalars["i"].which() == ValueTypeWhich::Number);
562 RandomVariable rv2 = QuantLib::ext::get<RandomVariable>(context2->scalars["i"]);
563 BOOST_CHECK(rv2.deterministic());
564 BOOST_CHECK_CLOSE(rv2.at(0), 2.0, tol);
565
566 // do not find date
567 auto context3 = QuantLib::ext::make_shared<Context>();
568 context3->arrays["a"] = dates;
569 context3->scalars["d"] = EventVec{nPaths, Date(15, June, 2020)};
570 ScriptEngine engine3(parser.ast(), context3, model);
571 BOOST_REQUIRE_NO_THROW(engine3.run());
572 BOOST_REQUIRE(context3->scalars["i"].which() == ValueTypeWhich::Number);
573 RandomVariable rv3 = QuantLib::ext::get<RandomVariable>(context3->scalars["i"]);
574 BOOST_CHECK(rv3.deterministic());
575 BOOST_CHECK_CLOSE(rv3.at(0), 0.0, tol);
576
577 // search in a number array => expect to not find date (but no error)
578 auto context4 = QuantLib::ext::make_shared<Context>();
579 std::vector<ValueType> numbers(5, RandomVariable(nPaths));
580 context4->arrays["a"] = numbers;
581 context4->scalars["d"] = EventVec{nPaths, Date(15, June, 2020)};
582 ScriptEngine engine4(parser.ast(), context4, model);
583 BOOST_REQUIRE_NO_THROW(engine4.run());
584 BOOST_REQUIRE(context4->scalars["i"].which() == ValueTypeWhich::Number);
585 RandomVariable rv4 = QuantLib::ext::get<RandomVariable>(context4->scalars["i"]);
586 BOOST_CHECK(rv4.deterministic());
587 BOOST_CHECK_CLOSE(rv4.at(0), 0.0, tol);
588
589 // value to find is not a date => error
590 auto context5 = QuantLib::ext::make_shared<Context>();
591 context5->arrays["a"] = dates;
592 context5->scalars["d"] = RandomVariable(nPaths);
593 ScriptEngine engine5(parser.ast(), context5, model);
594 BOOST_CHECK_THROW(engine5.run(), QuantLib::Error);
595
596 // search array is actually a scalar => error
597 auto context6 = QuantLib::ext::make_shared<Context>();
598 context6->scalars["a"] = EventVec{nPaths, Date(15, June, 2020)};
599 context6->scalars["d"] = EventVec{nPaths, Date(15, June, 2020)};
600 ScriptEngine engine6(parser.ast(), context6, model);
601 BOOST_CHECK_THROW(engine6.run(), QuantLib::Error);
602}
603
604BOOST_AUTO_TEST_CASE(testDateIndexFunctionGeq) {
605 BOOST_TEST_MESSAGE("Testing DATEINDEX(...,...,GEQ) function");
606
607 std::string script = "NUMBER i;\n"
608 "i = DATEINDEX(d, a, GEQ);";
609 ScriptParser parser(script);
610 BOOST_REQUIRE(parser.success());
611 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
612
613 constexpr Size nPaths = 10;
614 constexpr Real tol = 1E-10;
615
616 auto model = QuantLib::ext::make_shared<DummyModel>(nPaths);
617
618 std::vector<ValueType> dates;
619 dates.push_back(EventVec{nPaths, Date(7, May, 2019)});
620 dates.push_back(EventVec{nPaths, Date(10, June, 2020)});
621
622 // find date at index 1 (exact match)
623 auto context1 = QuantLib::ext::make_shared<Context>();
624 context1->arrays["a"] = dates;
625 context1->scalars["d"] = EventVec{nPaths, Date(7, May, 2019)};
626 ScriptEngine engine1(parser.ast(), context1, model);
627 BOOST_REQUIRE_NO_THROW(engine1.run());
628 BOOST_REQUIRE(context1->scalars["i"].which() == ValueTypeWhich::Number);
629 RandomVariable rv1 = QuantLib::ext::get<RandomVariable>(context1->scalars["i"]);
630 BOOST_CHECK(rv1.deterministic());
631 BOOST_CHECK_CLOSE(rv1.at(0), 1.0, tol);
632
633 // find date at index 2 (exact match)
634 auto context2 = QuantLib::ext::make_shared<Context>();
635 context2->arrays["a"] = dates;
636 context2->scalars["d"] = EventVec{nPaths, Date(10, June, 2020)};
637 ScriptEngine engine2(parser.ast(), context2, model);
638 BOOST_REQUIRE_NO_THROW(engine2.run());
639 BOOST_REQUIRE(context2->scalars["i"].which() == ValueTypeWhich::Number);
640 RandomVariable rv2 = QuantLib::ext::get<RandomVariable>(context2->scalars["i"]);
641 BOOST_CHECK(rv2.deterministic());
642 BOOST_CHECK_CLOSE(rv2.at(0), 2.0, tol);
643
644 // do not find date
645 auto context3 = QuantLib::ext::make_shared<Context>();
646 context3->arrays["a"] = dates;
647 context3->scalars["d"] = EventVec{nPaths, Date(15, June, 2020)};
648 ScriptEngine engine3(parser.ast(), context3, model);
649 BOOST_REQUIRE_NO_THROW(engine3.run());
650 BOOST_REQUIRE(context3->scalars["i"].which() == ValueTypeWhich::Number);
651 RandomVariable rv3 = QuantLib::ext::get<RandomVariable>(context3->scalars["i"]);
652 BOOST_CHECK(rv3.deterministic());
653 BOOST_CHECK_CLOSE(rv3.at(0), 3.0, tol);
654
655 // find date at index1 (from earlier date)
656 auto context4 = QuantLib::ext::make_shared<Context>();
657 context4->arrays["a"] = dates;
658 context4->scalars["d"] = EventVec{nPaths, Date(2, May, 2019)};
659 ScriptEngine engine4(parser.ast(), context4, model);
660 BOOST_REQUIRE_NO_THROW(engine4.run());
661 BOOST_REQUIRE(context4->scalars["i"].which() == ValueTypeWhich::Number);
662 RandomVariable rv4 = QuantLib::ext::get<RandomVariable>(context4->scalars["i"]);
663 BOOST_CHECK(rv4.deterministic());
664 BOOST_CHECK_CLOSE(rv4.at(0), 1.0, tol);
665
666 // find date at index2 (from earlier date)
667 auto context5 = QuantLib::ext::make_shared<Context>();
668 context5->arrays["a"] = dates;
669 context5->scalars["d"] = EventVec{nPaths, Date(2, June, 2020)};
670 ScriptEngine engine5(parser.ast(), context5, model);
671 BOOST_REQUIRE_NO_THROW(engine5.run());
672 BOOST_REQUIRE(context5->scalars["i"].which() == ValueTypeWhich::Number);
673 RandomVariable rv5 = QuantLib::ext::get<RandomVariable>(context5->scalars["i"]);
674 BOOST_CHECK(rv5.deterministic());
675 BOOST_CHECK_CLOSE(rv5.at(0), 2.0, tol);
676}
677
678BOOST_AUTO_TEST_CASE(testFwdCompFunction) {
679 BOOST_TEST_MESSAGE("Testing FWDCOMP() function");
680
681 Date ref(7, May, 2019);
682 Settings::instance().evaluationDate() = ref;
683
684 std::string script = "NUMBER rate;\n"
685 "rate = FWDCOMP(underlying, obs, start, end, spread, gearing);\n";
686 ScriptParser parser(script);
687 BOOST_REQUIRE(parser.success());
688 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
689
690 constexpr Size nPaths = 10; // does not matter, we use a model with deterministic rates below
691
692 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(ref, 0.02, ActualActual(ActualActual::ISDA)));
693 auto on = QuantLib::ext::make_shared<Eonia>(yts);
694
695 Date start = Date(10, October, 2018);
696 Date end = Date(10, October, 2019);
697 std::string indexName = "EUR-EONIA";
698 Real spread = 0.0;
699 Real gearing = 1.0;
700
701 std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<InterestRateIndex>>> irIndices;
702 irIndices.push_back(std::make_pair(indexName, on));
703 Model::McParams mcParams;
704 mcParams.regressionOrder = 1;
705 auto model = QuantLib::ext::make_shared<BlackScholes>(
706 nPaths, std::vector<std::string>{"EUR"}, std::vector<Handle<YieldTermStructure>>{yts},
707 std::vector<Handle<Quote>>(), irIndices,
708 std::vector<std::pair<std::string, QuantLib::ext::shared_ptr<ZeroInflationIndex>>>(), std::vector<std::string>(),
709 std::vector<std::string>(), Handle<BlackScholesModelWrapper>(QuantLib::ext::make_shared<BlackScholesModelWrapper>()),
710 std::map<std::pair<std::string, std::string>, Handle<QuantExt::CorrelationTermStructure>>(), mcParams,
711 std::set<Date>{});
712
713 auto context = QuantLib::ext::make_shared<Context>();
714 context->scalars["underlying"] = IndexVec{nPaths, indexName};
715 context->scalars["obs"] = EventVec{nPaths, start};
716 context->scalars["start"] = EventVec{nPaths, start};
717 context->scalars["end"] = EventVec{nPaths, end};
718 context->scalars["spread"] = RandomVariable(nPaths, spread);
719 context->scalars["gearing"] = RandomVariable(nPaths, gearing);
720
721 QuantExt::OvernightIndexedCoupon coupon(end, 1.0, start, end, on, gearing, spread);
722
723 auto indexInfo = QuantLib::ext::make_shared<StaticAnalyser>(parser.ast(), context);
724 BOOST_REQUIRE_NO_THROW(indexInfo->run(););
725 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgFixingDates().size(), 1);
726 BOOST_REQUIRE(indexInfo->fwdCompAvgFixingDates().at(indexName).size() == coupon.fixingDates().size());
727 Size i = 0;
728 for (auto const& f : indexInfo->fwdCompAvgFixingDates().at(indexName)) {
729 BOOST_CHECK_EQUAL(f, coupon.fixingDates()[i++]);
730 }
731 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgEvalDates().size(), 1);
732 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgStartEndDates().size(), 1);
733 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgEvalDates().at(indexName).size(), 1);
734 BOOST_CHECK_EQUAL(*indexInfo->fwdCompAvgEvalDates().at(indexName).begin(), start);
735 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgStartEndDates().size(), 1);
736 BOOST_REQUIRE_EQUAL(indexInfo->fwdCompAvgStartEndDates().at(indexName).size(), 2);
737 auto it = indexInfo->fwdCompAvgStartEndDates().at(indexName).begin();
738 BOOST_CHECK_EQUAL(*it++, start);
739 BOOST_CHECK_EQUAL(*it, end);
740
741 for (auto const& d : indexInfo->fwdCompAvgFixingDates().at(indexName)) {
742 on->addFixing(d, 0.01);
743 }
744
745 ScriptEngine engine(parser.ast(), context, model);
746 BOOST_REQUIRE_NO_THROW(engine.run());
747 BOOST_REQUIRE(context->scalars["rate"].which() == ValueTypeWhich::Number);
748 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars["rate"]);
749 BOOST_TEST_MESSAGE("rate from engine = " << rv.at(0) << " rate from coupon = " << coupon.rate());
750 BOOST_CHECK_CLOSE(rv.at(0), coupon.rate(), 1E-10);
751}
752
753BOOST_AUTO_TEST_CASE(testProbFunctions) {
754 BOOST_TEST_MESSAGE("Testing ABOVEPROB(), BELOWPROB() functions");
755
756 Date ref(7, May, 2019);
757 Settings::instance().evaluationDate() = ref;
758
759 std::string script = "AboveProb = ABOVEPROB(Underlying, Date1, Date2, BarrierUp);\n"
760 "BelowProb = BELOWPROB(Underlying, Date1, Date2, BarrierDown);\n";
761 ScriptParser parser(script);
762 BOOST_REQUIRE(parser.success());
763 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
764
765 Size nPaths = 10000;
766
767 Real s0 = 100.0;
768 Real vol = 0.10;
769 Date date1(7, May, 2020);
770 Date date2(7, December, 2020);
771 Real barrierUp = 110.0;
772 Real barrierDown = 80.0;
773
774 auto context = QuantLib::ext::make_shared<Context>();
775 context->scalars["Underlying"] = IndexVec{nPaths, "EQ-Dummy"};
776 context->scalars["Date1"] = EventVec{nPaths, date1};
777 context->scalars["Date2"] = EventVec{nPaths, date2};
778 context->scalars["BarrierUp"] = RandomVariable(nPaths, barrierUp);
779 context->scalars["BarrierDown"] = RandomVariable(nPaths, barrierDown);
780 context->scalars["AboveProb"] = RandomVariable(nPaths, barrierUp);
781 context->scalars["BelowProb"] = RandomVariable(nPaths, barrierDown);
782
783 Handle<YieldTermStructure> yts0(QuantLib::ext::make_shared<FlatForward>(ref, 0.0, ActualActual(ActualActual::ISDA)));
784 Handle<BlackVolTermStructure> volts(
785 QuantLib::ext::make_shared<BlackConstantVol>(ref, NullCalendar(), vol, ActualActual(ActualActual::ISDA)));
786 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
787 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts0, volts);
788 std::set<Date> simulationDates = {date1, date2};
789 Model::McParams mcParams;
790 mcParams.regressionOrder = 1;
791 auto model = QuantLib::ext::make_shared<BlackScholes>(
792 nPaths, "USD", yts0, "EQ-Dummy", "USD",
793 BlackScholesModelBuilder(yts0, process, simulationDates, std::set<Date>(), 1).model(), mcParams, simulationDates);
794
795 ScriptEngine engine(parser.ast(), context, model);
796 BOOST_REQUIRE_NO_THROW(engine.run());
797 BOOST_REQUIRE(context->scalars["AboveProb"].which() == ValueTypeWhich::Number);
798 BOOST_REQUIRE(context->scalars["BelowProb"].which() == ValueTypeWhich::Number);
799 RandomVariable rvAbove = QuantLib::ext::get<RandomVariable>(context->scalars["AboveProb"]);
800 RandomVariable rvBelow = QuantLib::ext::get<RandomVariable>(context->scalars["BelowProb"]);
801 BOOST_REQUIRE(rvAbove.size() == nPaths);
802 BOOST_REQUIRE(rvBelow.size() == nPaths);
803
804 Real avgAbove = expectation(rvAbove).at(0);
805 Real avgBelow = expectation(rvBelow).at(0);
806 BOOST_TEST_MESSAGE("prob estimation using ABOVEPROB(), BELOWPROB(): " << avgAbove << " (above), " << avgBelow
807 << " (below)");
808
809 // compute the probs using MC + a brute force check on a "fine" time grid
810 Size timeSteps = 500;
811 std::vector<Real> times;
812 Real t0 = process->riskFreeRate()->timeFromReference(date1);
813 Real t1 = process->riskFreeRate()->timeFromReference(date2);
814 times.push_back(t0);
815 for (Size i = 0; i < timeSteps; ++i) {
816 times.push_back(t0 + (t1 - t0) * static_cast<Real>(i + 1) / static_cast<Real>(timeSteps));
817 }
818 TimeGrid timeGrid(times.begin(), times.end());
819 auto pg = makeMultiPathGenerator(SobolBrownianBridge, process, timeGrid, SobolBrownianGenerator::Steps);
820 double avgAbove2 = 0.0, avgBelow2 = 0.0;
821 for (Size path = 0; path < nPaths; ++path) {
822 MultiPath p = pg->next().value;
823 // brute force check for barrier hit on time grid
824 bool hitAbove = false, hitBelow = false;
825 for (Size i = 1; i < timeGrid.size(); ++i) {
826 if (p[0][i] > barrierUp)
827 hitAbove = true;
828 else if (p[0][i] < barrierDown)
829 hitBelow = true;
830 }
831 if (hitAbove)
832 avgAbove2 += 1.0 / static_cast<Real>(nPaths);
833 if (hitBelow)
834 avgBelow2 += 1.0 / static_cast<Real>(nPaths);
835 }
836 BOOST_TEST_MESSAGE("prob estimation using MC (timeSteps=" << timeSteps << "): " << avgAbove2 << " (above), "
837 << avgBelow2 << " (below)");
838 BOOST_CHECK_CLOSE(avgAbove, avgAbove2, 5.0);
839 BOOST_CHECK_CLOSE(avgBelow, avgBelow2, 5.0);
840
841 // compute the probs using an analytical formula on the start and end point
842 std::vector<Real> times2 = {t0, t1};
843 TimeGrid timeGrid2(times2.begin(), times2.end());
844 auto pg2 = makeMultiPathGenerator(SobolBrownianBridge, process, timeGrid2, SobolBrownianGenerator::Steps);
845 double checkAvgAbove = 0.0, checkAvgBelow = 0.0;
846 for (Size path = 0; path < nPaths; ++path) {
847 MultiPath p = pg2->next().value;
848 Real v1 = p[0][1], v2 = p[0][2];
849 Real pAbove, pBelow;
850 if (v1 > barrierUp || v2 > barrierUp)
851 pAbove = 1.0;
852 else
853 pAbove = std::exp(-2.0 / (vol * vol * (t1 - t0)) * std::log(v1 / barrierUp) * std::log(v2 / barrierUp));
854 if (v1 < barrierDown || v2 < barrierDown)
855 pBelow = 1.0;
856 else
857 pBelow = std::exp(-2.0 / (vol * vol * (t1 - t0)) * std::log(v1 / barrierDown) * std::log(v2 / barrierDown));
858 checkAvgAbove += pAbove / static_cast<Real>(nPaths);
859 checkAvgBelow += pBelow / static_cast<Real>(nPaths);
860 }
861 BOOST_TEST_MESSAGE("prob estimation using MC + analytical formula for endpoints: " << checkAvgAbove << " (above), "
862 << checkAvgBelow << " (below)");
863 BOOST_CHECK_CLOSE(avgAbove, checkAvgAbove, 1.0E-4);
864 BOOST_CHECK_CLOSE(avgBelow, checkAvgBelow, 1.0E-4);
865}
866
867BOOST_AUTO_TEST_CASE(testEuropeanOption) {
868 BOOST_TEST_MESSAGE("Testing european option...");
869
870 Date ref(7, May, 2019);
871 Settings::instance().evaluationDate() = ref;
872
873 std::string script = "Option = Quantity * PAY(max( PutCall * (Underlying(Expiry) - Strike), 0 ),\n"
874 " Expiry, Settlement, PayCcy);";
875 ScriptParser parser(script);
876 BOOST_REQUIRE(parser.success());
877 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
878
879 Real s0 = 100.0;
880 Real vol = 0.18;
881 Real rate = 0.02;
882 Real quantity = 10.0;
883 Real putcall = 1.0;
884 Real strike = 100.0;
885 Date expiry(7, May, 2020);
886 Date settlement(9, May, 2020);
887
888 constexpr Size nPaths = 50000;
889
890 auto context = QuantLib::ext::make_shared<Context>();
891 context->scalars["Quantity"] = RandomVariable(nPaths, quantity);
892 context->scalars["PutCall"] = RandomVariable(nPaths, putcall);
893 context->scalars["Strike"] = RandomVariable(nPaths, strike);
894 context->scalars["Underlying"] = IndexVec{nPaths, "EQ-SP5"};
895 context->scalars["Expiry"] = EventVec{nPaths, expiry};
896 context->scalars["Settlement"] = EventVec{nPaths, settlement};
897 context->scalars["PayCcy"] = CurrencyVec{nPaths, "USD"};
898 context->scalars["Option"] = RandomVariable(nPaths, 0.0);
899
900 auto indexInfo = QuantLib::ext::make_shared<StaticAnalyser>(parser.ast(), context);
901 BOOST_REQUIRE_NO_THROW(indexInfo->run());
902 BOOST_REQUIRE_EQUAL(indexInfo->indexEvalDates().size(), 1);
903 BOOST_CHECK_EQUAL(indexInfo->indexEvalDates().begin()->first, "EQ-SP5");
904 BOOST_CHECK_EQUAL(indexInfo->indexEvalDates().begin()->second.size(), 1);
905 BOOST_CHECK_EQUAL(*indexInfo->indexEvalDates().begin()->second.begin(), expiry);
906 BOOST_REQUIRE_EQUAL(indexInfo->payObsDates().size(), 1);
907 BOOST_CHECK_EQUAL(indexInfo->payObsDates().begin()->first, "USD");
908 BOOST_CHECK_EQUAL(indexInfo->payObsDates().begin()->second.size(), 1);
909 BOOST_CHECK_EQUAL(*indexInfo->payObsDates().begin()->second.begin(), expiry);
910 BOOST_REQUIRE_EQUAL(indexInfo->payPayDates().size(), 1);
911 BOOST_CHECK_EQUAL(indexInfo->payPayDates().begin()->first, "USD");
912 BOOST_CHECK_EQUAL(indexInfo->payPayDates().begin()->second.size(), 1);
913 BOOST_CHECK_EQUAL(*indexInfo->payPayDates().begin()->second.begin(), settlement);
914 BOOST_CHECK(indexInfo->regressionDates().empty());
915
916 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(ref, rate, ActualActual(ActualActual::ISDA)));
917 Handle<YieldTermStructure> yts0(QuantLib::ext::make_shared<FlatForward>(ref, 0.0, ActualActual(ActualActual::ISDA)));
918 Handle<BlackVolTermStructure> volts(
919 QuantLib::ext::make_shared<BlackConstantVol>(ref, NullCalendar(), vol, ActualActual(ActualActual::ISDA)));
920 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
921 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
922
923 // compile the sim and pay dates required by the BS model ctor below
924 std::set<Date> simulationDates, payDates;
925 for (auto const& s : indexInfo->indexEvalDates())
926 simulationDates.insert(s.second.begin(), s.second.end());
927 for (auto const& s : indexInfo->payObsDates())
928 simulationDates.insert(s.second.begin(), s.second.end());
929 simulationDates.insert(indexInfo->regressionDates().begin(), indexInfo->regressionDates().end());
930 for (auto const& s : indexInfo->payPayDates())
931 payDates.insert(s.second.begin(), s.second.end());
932
933 cpu_timer timer;
934 Model::McParams mcParams;
935 mcParams.regressionOrder = 6;
936 auto model = QuantLib::ext::make_shared<BlackScholes>(
937 nPaths, "USD", yts, "EQ-SP5", "USD",
938 BlackScholesModelBuilder(yts, process, simulationDates, payDates, 1).model(), mcParams, simulationDates);
939 ScriptEngine engine(parser.ast(), context, model);
940 BOOST_REQUIRE_NO_THROW(engine.run());
941 BOOST_REQUIRE(context->scalars["Option"].which() == ValueTypeWhich::Number);
942 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars["Option"]);
943 BOOST_REQUIRE(rv.size() == nPaths);
944 Real avg = expectation(rv).at(0);
945 timer.stop();
946 BOOST_TEST_MESSAGE("option value estimation " << avg << " (timing " << timer.format(default_places, "%w") << "s)");
947
948 // hardcoded version of the script
949 timer.start();
950 std::vector<Real> times;
951 times.push_back(process->riskFreeRate()->timeFromReference(expiry));
952 auto pg = makeMultiPathGenerator(SobolBrownianBridge, process, TimeGrid(times.begin(), times.end()),
953 SobolBrownianGenerator::Steps);
954 double avg2 = 0.0;
955 for (Size path = 0; path < nPaths; ++path) {
956 MultiPath p = pg->next().value;
957 Real v = quantity * std::max(putcall * (p[0][1] - strike), 0.0);
958 avg2 += v;
959 }
960 avg2 *= process->riskFreeRate()->discount(settlement) / static_cast<double>(nPaths);
961 timer.stop();
962 BOOST_TEST_MESSAGE("result with hardcoded script " << avg2 << " (timing " << timer.format(default_places, "%w")
963 << "s)");
964 BOOST_CHECK_CLOSE(avg, avg2, 1E-10);
965
966 // analytical computation
967 double expected =
968 quantity * blackFormula(Option::Call, s0, s0 / yts->discount(expiry),
969 vol * std::sqrt(yts->timeFromReference(expiry)), yts->discount(settlement));
970 BOOST_TEST_MESSAGE("option value expected " << expected);
971 BOOST_CHECK_CLOSE(avg, expected, 0.1);
972}
973
974BOOST_AUTO_TEST_CASE(testAmericanOption) {
975 BOOST_TEST_MESSAGE("Testing american option...");
976
977 Date ref(7, May, 2019);
978 Settings::instance().evaluationDate() = ref;
979
980 std::string script = "NUMBER Exercise;\n"
981 "NUMBER i;\n"
982 "FOR i IN (SIZE(Expiry), 1, -11) DO\n"
983 " Exercise = PAY( PutCall * (Underlying(Expiry[i]) - Strike),\n"
984 " Expiry[i], Settlement[i], PayCcy );\n"
985 " IF Exercise > NPV( Option, Expiry[i], Exercise > 0 ) AND Exercise > 0 THEN\n"
986 " Option = Exercise;\n"
987 " END;\n"
988 "END;\n"
989 "Option = Quantity * Option;\n";
990
991 ScriptParser parser(script);
992 BOOST_REQUIRE(parser.success());
993 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
994
995 Real s0 = 100.0;
996 Real vol = 0.18;
997 Real rate = 0.01;
998 Real quantity = 10.0;
999 Real putcall = -1.0;
1000 Real strike = 100.0;
1001
1002 constexpr Size nPaths = 100000;
1003
1004 Schedule expirySchedule(Date(8, May, 2019), Date(9, May, 2020), 1 * Weeks, NullCalendar(), Unadjusted, Unadjusted,
1005 DateGeneration::Forward, false);
1006 std::vector<ValueType> expiryDates, settlDates;
1007 for (auto const& d : expirySchedule.dates()) {
1008 expiryDates.push_back(EventVec{nPaths, d});
1009 settlDates.push_back(EventVec{nPaths, d /* + 2*/}); // for comparison with fd engine set settlment = expiry
1010 }
1011
1012 auto context = QuantLib::ext::make_shared<Context>();
1013 context->scalars["Quantity"] = RandomVariable(nPaths, quantity);
1014 context->scalars["PutCall"] = RandomVariable(nPaths, putcall);
1015 context->scalars["Strike"] = RandomVariable(nPaths, strike);
1016 context->scalars["Underlying"] = IndexVec{nPaths, "EQ-SP5"};
1017 context->arrays["Expiry"] = expiryDates;
1018 context->arrays["Settlement"] = settlDates;
1019 context->scalars["PayCcy"] = CurrencyVec{nPaths, "USD"};
1020 context->scalars["Option"] = RandomVariable(nPaths, 0.0);
1021
1022 auto indexInfo = QuantLib::ext::make_shared<StaticAnalyser>(parser.ast(), context);
1023 BOOST_REQUIRE_NO_THROW(indexInfo->run());
1024
1025 // compile the sim and pay dates required by the BS model ctor below
1026 std::set<Date> simulationDates, payDates;
1027 for (auto const& s : indexInfo->indexEvalDates())
1028 simulationDates.insert(s.second.begin(), s.second.end());
1029 for (auto const& s : indexInfo->payObsDates())
1030 simulationDates.insert(s.second.begin(), s.second.end());
1031 simulationDates.insert(indexInfo->regressionDates().begin(), indexInfo->regressionDates().end());
1032 for (auto const& s : indexInfo->payPayDates())
1033 payDates.insert(s.second.begin(), s.second.end());
1034
1035 BOOST_REQUIRE_EQUAL(simulationDates.size(), expiryDates.size());
1036
1037 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(ref, rate, ActualActual(ActualActual::ISDA)));
1038 Handle<YieldTermStructure> yts0(QuantLib::ext::make_shared<FlatForward>(ref, 0.0, ActualActual(ActualActual::ISDA)));
1039 Handle<BlackVolTermStructure> volts(
1040 QuantLib::ext::make_shared<BlackConstantVol>(ref, NullCalendar(), vol, ActualActual(ActualActual::ISDA)));
1041 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
1042 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
1043
1044 cpu_timer timer;
1045 Model::McParams mcParams;
1046 mcParams.regressionOrder = 6;
1047 auto model = QuantLib::ext::make_shared<BlackScholes>(
1048 nPaths, "USD", yts, "EQ-SP5", "USD",
1049 BlackScholesModelBuilder(yts, process, simulationDates, payDates, 1).model(), mcParams, simulationDates);
1050 ScriptEngine engine(parser.ast(), context, model);
1051 BOOST_REQUIRE_NO_THROW(engine.run());
1052 BOOST_TEST_MESSAGE(*context);
1053 BOOST_REQUIRE(context->scalars["Option"].which() == ValueTypeWhich::Number);
1054 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars["Option"]);
1055 BOOST_REQUIRE(rv.size() == nPaths);
1056 Real avg = expectation(rv).at(0);
1057 timer.stop();
1058 BOOST_TEST_MESSAGE("option value estimation " << avg << " (timing " << timer.format(default_places, "%w") << "s)");
1059
1060 // compare with result from fd engine
1061 auto fdEngine = QuantLib::ext::make_shared<FdBlackScholesVanillaEngine>(process, 100, 100);
1062 VanillaOption option(QuantLib::ext::make_shared<PlainVanillaPayoff>(putcall > 0.0 ? Option::Call : Option::Put, strike),
1063 QuantLib::ext::make_shared<AmericanExercise>(ref, expirySchedule.dates().back()));
1064 option.setPricingEngine(fdEngine);
1065 timer.start();
1066 Real fdNpv = option.NPV() * quantity;
1067 timer.stop();
1068 BOOST_TEST_MESSAGE("fd engine result " << fdNpv << " (timing " << timer.format(default_places, "%w") << "s)");
1069 BOOST_CHECK_CLOSE(avg, fdNpv, 5.0);
1070}
1071
1072BOOST_AUTO_TEST_CASE(testAsianOption) {
1073 BOOST_TEST_MESSAGE("Testing asian option...");
1074
1075 Date ref(7, May, 2019);
1076 Settings::instance().evaluationDate() = ref;
1077
1078 std::string script = "NUMBER avg; NUMBER i;"
1079 "FOR i IN (1,SIZE(ObservationDates),1) DO"
1080 " avg = avg + Underlying(ObservationDates[i]);"
1081 "END;"
1082 "Option = Quantity * PAY( max( PutCall * (avg / SIZE(ObservationDates) - Strike), 0),"
1083 " Settlement, Settlement, PayCcy);";
1084
1085 ScriptParser parser(script);
1086 BOOST_REQUIRE(parser.success());
1087 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
1088
1089 Real s0 = 100.0;
1090 Real vol = 0.18;
1091 Real rate = 0.02;
1092 Real quantity = 10.0;
1093 Real putcall = 1.0;
1094 Real strike = 100.0;
1095
1096 constexpr Size nPaths = 10000;
1097
1098 Schedule observationSchedule(Date(9, May, 2019), Date(9, May, 2020), 1 * Weeks, NullCalendar(), Unadjusted,
1099 Unadjusted, DateGeneration::Forward, false);
1100 std::vector<ValueType> observationDates;
1101 for (auto const& d : observationSchedule.dates())
1102 observationDates.push_back(EventVec{nPaths, d});
1103
1104 auto context = QuantLib::ext::make_shared<Context>();
1105 context->scalars["Quantity"] = RandomVariable(nPaths, quantity);
1106 context->scalars["PutCall"] = RandomVariable(nPaths, putcall);
1107 context->scalars["Strike"] = RandomVariable(nPaths, strike);
1108 context->scalars["Underlying"] = IndexVec{nPaths, "EQ-SP5"};
1109 context->arrays["ObservationDates"] = observationDates;
1110 context->scalars["Settlement"] = observationDates.back();
1111 context->scalars["PayCcy"] = CurrencyVec{nPaths, "USD"};
1112 context->scalars["Option"] = RandomVariable(nPaths, 0.0);
1113
1114 auto indexInfo = QuantLib::ext::make_shared<StaticAnalyser>(parser.ast(), context);
1115 BOOST_REQUIRE_NO_THROW(indexInfo->run());
1116
1117 // compile the sim and pay dates required by the BS model ctor below
1118 std::set<Date> simulationDates, payDates;
1119 for (auto const& s : indexInfo->indexEvalDates())
1120 simulationDates.insert(s.second.begin(), s.second.end());
1121 for (auto const& s : indexInfo->payObsDates())
1122 simulationDates.insert(s.second.begin(), s.second.end());
1123 simulationDates.insert(indexInfo->regressionDates().begin(), indexInfo->regressionDates().end());
1124 for (auto const& s : indexInfo->payPayDates())
1125 payDates.insert(s.second.begin(), s.second.end());
1126
1127 BOOST_REQUIRE_EQUAL(indexInfo->indexEvalDates().size(), 1);
1128 BOOST_CHECK_EQUAL(indexInfo->indexEvalDates().begin()->first, "EQ-SP5");
1129 BOOST_REQUIRE_EQUAL(simulationDates.size(), observationDates.size());
1130 Size i = 0;
1131 for (auto const& d : simulationDates)
1132 BOOST_CHECK_EQUAL(d, QuantLib::ext::get<EventVec>(observationDates[i++]).value);
1133
1134 Handle<YieldTermStructure> yts(QuantLib::ext::make_shared<FlatForward>(ref, rate, ActualActual(ActualActual::ISDA)));
1135 Handle<YieldTermStructure> yts0(QuantLib::ext::make_shared<FlatForward>(ref, 0.0, ActualActual(ActualActual::ISDA)));
1136 Handle<BlackVolTermStructure> volts(
1137 QuantLib::ext::make_shared<BlackConstantVol>(ref, NullCalendar(), vol, ActualActual(ActualActual::ISDA)));
1138 auto process = QuantLib::ext::make_shared<GeneralizedBlackScholesProcess>(
1139 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(s0)), yts0, yts, volts);
1140
1141 cpu_timer timer;
1142 Model::McParams mcParams;
1143 mcParams.regressionOrder = 6;
1144 auto model = QuantLib::ext::make_shared<BlackScholes>(
1145 nPaths, "USD", yts, "EQ-SP5", "USD",
1146 BlackScholesModelBuilder(yts, process, simulationDates, payDates, 1).model(), mcParams, simulationDates);
1147 ScriptEngine engine(parser.ast(), context, model);
1148 BOOST_REQUIRE_NO_THROW(engine.run());
1149 BOOST_REQUIRE(context->scalars["Option"].which() == ValueTypeWhich::Number);
1150 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars["Option"]);
1151 BOOST_REQUIRE(rv.size() == nPaths);
1152 Real avg = expectation(rv).at(0);
1153 timer.stop();
1154 BOOST_TEST_MESSAGE("option value estimation " << avg << " (timing " << timer.format(default_places, "%w") << "s)");
1155
1156 // hardcoded version of the script
1157 std::vector<Real> times;
1158 for (auto const& d : observationDates)
1159 times.push_back(process->riskFreeRate()->timeFromReference(QuantLib::ext::get<EventVec>(d).value));
1160 auto pg = makeMultiPathGenerator(SobolBrownianBridge, process, TimeGrid(times.begin(), times.end()),
1161 SobolBrownianGenerator::Steps);
1162 double avg2 = 0.0;
1163 timer.start();
1164 for (Size path = 0; path < nPaths; ++path) {
1165 MultiPath p = pg->next().value;
1166 Real payoff = 0.0;
1167 for (Size i = 1; i < p[0].length(); ++i)
1168 payoff += p[0][i];
1169 Real v = std::max(payoff / static_cast<double>(observationDates.size()) - strike, 0.0);
1170 avg2 += v;
1171 }
1172 avg2 *= quantity * process->riskFreeRate()->discount(QuantLib::ext::get<EventVec>(observationDates.back()).value) /
1173 static_cast<double>(nPaths);
1174 timer.stop();
1175 BOOST_TEST_MESSAGE("result with hardcoded script " << avg2 << "(timing " << timer.format(default_places, "%w")
1176 << "s)");
1177 BOOST_CHECK_CLOSE(avg, avg2, 1E-10);
1178}
1179
1180BOOST_AUTO_TEST_CASE(testAutocallable) {
1181 BOOST_TEST_MESSAGE("Testing autocallable...");
1182
1183 Date ref(7, May, 2019);
1184 Settings::instance().evaluationDate() = ref;
1185
1186 std::string script =
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
1217 ScriptParser parser(script);
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(); // inclusive the strike date
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>();
1245 context->scalars["Notional"] = RandomVariable(nPaths, notional);
1246 context->scalars["Strike"] = RandomVariable(nPaths, strike);
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"};
1253 context->scalars["Option"] = RandomVariable(nPaths, 0.0);
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 // compile the sim and pay dates required by the BS model ctor below
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;
1312 Model::McParams mcParams;
1313 mcParams.regressionOrder = 6;
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"),
1319 BlackScholesModelBuilder({yts}, processesBs, simulationDates, payDates, 24).model(), correlations, mcParams,
1320 simulationDates);
1321 ScriptEngine engine(parser.ast(), context, model);
1322 BOOST_REQUIRE_NO_THROW(engine.run());
1323 BOOST_REQUIRE(context->scalars["Option"].which() == ValueTypeWhich::Number);
1324 RandomVariable rv = QuantLib::ext::get<RandomVariable>(context->scalars["Option"]);
1325 BOOST_REQUIRE(rv.size() == nPaths);
1326 Real avg = expectation(rv).at(0);
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 // hardcoded version of the script
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);
1347 auto pg = makeMultiPathGenerator(SobolBrownianBridge, process, grid, SobolBrownianGenerator::Steps);
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 // script translated to c++
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 // script translation end
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 // 1% tolerance, since hardcoded impl uses pseudoSqrt() while BlackScholes uses CholeskyDecomposition
1393 BOOST_CHECK_CLOSE(avg, avg2, 1.0);
1394}
1395
1396BOOST_AUTO_TEST_CASE(testNestedIfThenElse) {
1397 BOOST_TEST_MESSAGE("Testing nested if-then-else statements...");
1398
1399 std::string script = "IF x < 8 THEN\n"
1400 " IF x < 4 THEN\n"
1401 " IF x < 2 THEN\n"
1402 " IF x < 1 THEN\n"
1403 " y = 0;\n"
1404 " ELSE\n"
1405 " y = 1;\n"
1406 " END;\n"
1407 " ELSE\n"
1408 " IF x < 3 THEN\n"
1409 " y = 2;\n"
1410 " ELSE\n"
1411 " y = 3;\n"
1412 " END;\n"
1413 " END;\n"
1414 " ELSE\n"
1415 " IF x < 6 THEN\n"
1416 " IF x < 5 THEN\n"
1417 " y = 4;\n"
1418 " ELSE\n"
1419 " y = 5;\n"
1420 " END;\n"
1421 " ELSE\n"
1422 " IF x < 7 THEN\n"
1423 " y = 6;\n"
1424 " ELSE\n"
1425 " y = 7;\n"
1426 " END;\n"
1427 " END;\n"
1428 " END;\n"
1429 "ELSE\n"
1430 " IF x < 12 THEN\n"
1431 " IF x < 10 THEN\n"
1432 " IF x < 9 THEN\n"
1433 " y = 8;\n"
1434 " ELSE\n"
1435 " y = 9;\n"
1436 " END;\n"
1437 " ELSE\n"
1438 " IF x < 11 THEN\n"
1439 " y = 10;\n"
1440 " ELSE\n"
1441 " y = 11;\n"
1442 " END;\n"
1443 " END;\n"
1444 " ELSE\n"
1445 " IF x < 14 THEN\n"
1446 " IF x < 13 THEN\n"
1447 " y = 12;\n"
1448 " ELSE\n"
1449 " y = 13;\n"
1450 " END;\n"
1451 " ELSE\n"
1452 " IF x < 15 THEN\n"
1453 " y = 14;\n"
1454 " ELSE\n"
1455 " y = 15;\n"
1456 " END;\n"
1457 " END;\n"
1458 " END;\n"
1459 "END;\n";
1460
1461 ScriptParser parser(script);
1462 BOOST_REQUIRE(parser.success());
1463 BOOST_TEST_MESSAGE("Parsing successful, AST:\n" << to_string(parser.ast()));
1464
1465 auto context = QuantLib::ext::make_shared<Context>();
1466
1467 RandomVariable x(16), y(16);
1468 for (Size i = 0; i < 16; ++i)
1469 x.set(i, static_cast<Real>(i));
1470
1471 context->scalars["x"] = x;
1472 context->scalars["y"] = y;
1473
1474 ScriptEngine engine(parser.ast(), context, QuantLib::ext::make_shared<DummyModel>(16));
1475 BOOST_REQUIRE_NO_THROW(engine.run());
1476 BOOST_TEST_MESSAGE("Script Engine successfully run, context is:\n" << *context);
1477
1478 BOOST_REQUIRE(y.size() == 16);
1479 for (Size i = 0; i < 16; ++i) {
1480 BOOST_CHECK_EQUAL(x.at(i), i);
1481 }
1482}
1483
1484BOOST_AUTO_TEST_CASE(testInteractive, *boost::unit_test::disabled()) {
1485
1486 // not a test, just for convenience, to be removed at some stage...
1487
1488 BOOST_TEST_MESSAGE("Running Script Engine on INPUT env variable...");
1489
1490 std::string script = "NUMBER i,x;"
1491 "FOR i IN (1,10,1) DO x=x+i; END;";
1492 if (auto c = getenv("INPUT"))
1493 script = std::string(c);
1494
1495 ScriptParser parser(script);
1496 if (parser.success()) {
1497 std::cerr << "Parsing succeeded\n" << to_string(parser.ast()) << std::endl;
1498 } else {
1499 std::cerr << "Parsing failed\n" << parser.error();
1500 }
1501
1502 auto context = QuantLib::ext::make_shared<Context>();
1503 ScriptEngine engine(parser.ast(), context, nullptr);
1504 try {
1505 engine.run();
1506 std::cerr << "Script successfully executed, context is:\n" << *context << std::endl;
1507 } catch (const std::exception& e) {
1508 std::cerr << "ERROR during script execution: " << e.what() << std::endl;
1509 std::cerr << *context << std::endl;
1510 }
1511}
1512
1513BOOST_AUTO_TEST_SUITE_END()
1514
1515BOOST_AUTO_TEST_SUITE_END()
ast printer
std::string script
black scholes model for n underlyings (fx, equity or commodity)
builder for an array of black scholes processes
const std::vector< Date > & fixingDates() const
DummyModel(const Size n)
Definition: dummymodel.hpp:33
const Date & referenceDate() const override
Definition: dummymodel.hpp:67
void run(const std::string &script="", bool interactive=false, QuantLib::ext::shared_ptr< PayLog > paylog=nullptr, bool includePastCashflows=false)
ASTNodePtr ast() const
const ParserError & error() const
SafeStack< ValueType > value
dummy model implementation
RandomVariable max(RandomVariable x, const RandomVariable &y)
RandomVariable exp(RandomVariable x)
RandomVariable sqrt(RandomVariable x)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
SobolBrownianBridge
RandomVariable log(RandomVariable x)
boost::shared_ptr< MultiPathGeneratorBase > makeMultiPathGenerator(const SequenceType s, const boost::shared_ptr< StochasticProcess > &process, const TimeGrid &timeGrid, const BigNatural seed, const SobolBrownianGenerator::Ordering ordering=SobolBrownianGenerator::Steps, const SobolRsg::DirectionIntegers directionIntegers=SobolRsg::JoeKuoD7)
RandomVariable pow(RandomVariable x, const RandomVariable &y)
bool close_enough_all(const RandomVariable &x, const RandomVariable &y)
RandomVariable expectation(const RandomVariable &r)
RandomVariable normalCdf(RandomVariable x)
RandomVariable abs(RandomVariable x)
RandomVariable normalPdf(RandomVariable x)
Filter equal(Filter x, const Filter &y)
RandomVariable min(RandomVariable x, const RandomVariable &y)
bool deterministic(const ValueType &v)
Definition: value.cpp:144
Size size(const ValueType &v)
Definition: value.cpp:145
std::string to_string(const LocationInfo &l)
Definition: ast.cpp:28
boost::variant< RandomVariable, EventVec, CurrencyVec, IndexVec, DaycounterVec, Filter > ValueType
Definition: value.hpp:60
scriptengine
script parser
static script analyser
Real at(const Size i) const
bool deterministic() const
void set(const Size i, const Real v)
BOOST_AUTO_TEST_CASE(testSimpleScript)