[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2015 Klaus Spanderen
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include "observable.hpp"
21#include "utilities.hpp"
22#include <ql/indexes/ibor/euribor.hpp>
23#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
24#include <ql/patterns/observable.hpp>
25#include <ql/quotes/simplequote.hpp>
26#include <ql/termstructures/volatility/capfloor/capfloortermvolsurface.hpp>
27#include <ql/termstructures/volatility/optionlet/strippedoptionlet.hpp>
28#include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp>
29#include <ql/termstructures/yield/flatforward.hpp>
30#include <ql/time/calendars/nullcalendar.hpp>
31#include <chrono>
32#include <thread>
33
34using namespace QuantLib;
35using namespace boost::unit_test_framework;
36
37namespace {
38
39 class UpdateCounter : public Observer {
40 public:
41 UpdateCounter() = default;
42 void update() override { ++counter_; }
43 Size counter() const { return counter_; }
44
45 private:
46 Size counter_ = 0;
47 };
48
49 class RestoreUpdates { // NOLINT(cppcoreguidelines-special-member-functions)
50 public:
51 ~RestoreUpdates() {
52 ObservableSettings::instance().enableUpdates();
53 }
54 };
55
56}
57
58void ObservableTest::testObservableSettings() {
59
60 BOOST_TEST_MESSAGE("Testing observable settings...");
61
62 const ext::shared_ptr<SimpleQuote> quote(new SimpleQuote(100.0));
63 UpdateCounter updateCounter;
64
65 updateCounter.registerWith(h: quote);
66 if (updateCounter.counter() != 0) {
67 BOOST_FAIL("update counter value is not zero");
68 }
69
70 quote->setValue(1.0);
71 if (updateCounter.counter() != 1) {
72 BOOST_FAIL("update counter value is not one");
73 }
74
75 ObservableSettings::instance().disableUpdates(deferred: false);
76 quote->setValue(2.0);
77 if (updateCounter.counter() != 1) {
78 BOOST_FAIL("update counter value is not one");
79 }
80 ObservableSettings::instance().enableUpdates();
81 if (updateCounter.counter() != 1) {
82 BOOST_FAIL("update counter value is not one");
83 }
84
85 ObservableSettings::instance().disableUpdates(deferred: true);
86 quote->setValue(3.0);
87 if (updateCounter.counter() != 1) {
88 BOOST_FAIL("update counter value is not one");
89 }
90 ObservableSettings::instance().enableUpdates();
91 if (updateCounter.counter() != 2) {
92 BOOST_FAIL("update counter value is not two");
93 }
94
95 UpdateCounter updateCounter2;
96 updateCounter2.registerWith(h: quote);
97 ObservableSettings::instance().disableUpdates(deferred: true);
98 for (Size i=0; i < 10; ++i) {
99 quote->setValue(Real(i));
100 }
101 if (updateCounter.counter() != 2) {
102 BOOST_FAIL("update counter value is not two");
103 }
104 ObservableSettings::instance().enableUpdates();
105 if (updateCounter.counter() != 3 || updateCounter2.counter() != 1) {
106 BOOST_FAIL("update counter values are not correct");
107 }
108}
109
110
111#ifdef QL_ENABLE_THREAD_SAFE_OBSERVER_PATTERN
112
113#include <atomic>
114#include <mutex>
115#include <thread>
116#include <boost/date_time/posix_time/posix_time_types.hpp>
117
118#include <list>
119
120namespace {
121
122 class MTUpdateCounter : public Observer {
123 public:
124 MTUpdateCounter() : counter_(0) {
125 ++instanceCounter_;
126 }
127 ~MTUpdateCounter() {
128 --instanceCounter_;
129 }
130 void update() {
131 ++counter_;
132 }
133 int counter() { return counter_; }
134 static int instanceCounter() { return instanceCounter_; }
135
136 private:
137 std::atomic<int> counter_;
138 static std::atomic<int> instanceCounter_;
139 };
140
141 std::atomic<int> MTUpdateCounter::instanceCounter_(0);
142
143 class GarbageCollector {
144 public:
145 GarbageCollector() : terminate_(false) { }
146
147 void addObj(const ext::shared_ptr<MTUpdateCounter>& updateCounter) {
148 std::lock_guard<std::mutex> lock(mutex_);
149 objList.push_back(updateCounter);
150 }
151
152 void run() {
153 while(!terminate_) {
154 Size objListSize;
155 {
156 std::lock_guard<std::mutex> lock(mutex_);
157 objListSize = objList.size();
158 }
159
160 if (objListSize > 20) {
161 // trigger gc
162 while (objListSize > 0) {
163 std::lock_guard<std::mutex> lock(mutex_);
164 objList.pop_front();
165 objListSize = objList.size();
166 }
167 }
168
169 std::this_thread::sleep_for(std::chrono::milliseconds(2));
170 }
171 objList.clear();
172 }
173
174 void terminate() {
175 terminate_ = true;
176 }
177 private:
178 std::mutex mutex_;
179 std::atomic<bool> terminate_;
180
181 std::list<ext::shared_ptr<MTUpdateCounter> > objList;
182 };
183}
184
185void ObservableTest::testAsyncGarbagCollector() {
186
187 BOOST_TEST_MESSAGE("Testing observer pattern with an asynchronous "
188 "garbage collector (JVM/.NET use case)...");
189
190 // This test core dumps if used with the ordinary implementation
191 // of the observer pattern (comparable situation
192 // in JVM or .NET eco systems).
193
194 const ext::shared_ptr<SimpleQuote> quote(new SimpleQuote(-1.0));
195
196 GarbageCollector gc;
197 std::thread workerThread(&GarbageCollector::run, &gc);
198
199 for (Size i=0; i < 10000; ++i) {
200 const ext::shared_ptr<MTUpdateCounter> observer(new MTUpdateCounter);
201 observer->registerWith(quote);
202 gc.addObj(observer);
203
204 for (Size j=0; j < 10; ++j)
205 quote->setValue(Real(j));
206 }
207
208 gc.terminate();
209 workerThread.join();
210
211 if (MTUpdateCounter::instanceCounter() != 0) {
212 BOOST_FAIL("garbage collection does not work.");
213 }
214}
215
216
217void ObservableTest::testMultiThreadingGlobalSettings() {
218 BOOST_TEST_MESSAGE("Testing observer global settings in a "
219 "multithreading environment...");
220
221 const ext::shared_ptr<SimpleQuote> quote(new SimpleQuote(-1.0));
222
223 ObservableSettings::instance().disableUpdates(true);
224
225 GarbageCollector gc;
226 std::thread workerThread(&GarbageCollector::run, &gc);
227
228 typedef std::list<ext::shared_ptr<MTUpdateCounter> > local_list_type;
229 local_list_type localList;
230
231 for (Size i=0; i < 4000; ++i) {
232 const ext::shared_ptr<MTUpdateCounter> observer(new MTUpdateCounter);
233 observer->registerWith(quote);
234
235 if ((i%4) == 0) {
236 localList.push_back(observer);
237 for (Size j=0; j < 5; ++j)
238 quote->setValue(Real(j));
239 }
240 gc.addObj(observer);
241 }
242
243 gc.terminate();
244 workerThread.join();
245
246 if (localList.size() != Size(MTUpdateCounter::instanceCounter())) {
247 BOOST_FAIL("garbage collection does not work.");
248 }
249
250 for (local_list_type::iterator iter = localList.begin();
251 iter != localList.end(); ++iter) {
252 if ((*iter)->counter() != 0) {
253 BOOST_FAIL("notification should have been blocked");
254 }
255 }
256
257 ObservableSettings::instance().enableUpdates();
258
259 for (local_list_type::iterator iter = localList.begin();
260 iter != localList.end(); ++iter) {
261 if ((*iter)->counter() != 1) {
262 BOOST_FAIL("only one notification should have been sent");
263 }
264 }
265}
266#endif
267
268void ObservableTest::testDeepUpdate() {
269 BOOST_TEST_MESSAGE("Testing deep update of observers...");
270
271 RestoreUpdates guard;
272
273 Date refDate = Settings::instance().evaluationDate();
274
275 ObservableSettings::instance().disableUpdates(deferred: true);
276
277 Handle<YieldTermStructure> yts(
278 ext::make_shared<FlatForward>(args: 0, args: NullCalendar(), args: 0.02, args: Actual365Fixed()));
279 ext::shared_ptr<IborIndex> ibor = ext::make_shared<Euribor>(args: 3 * Months, args&: yts);
280 ext::shared_ptr<SimpleQuote> q = ext::make_shared<SimpleQuote>(args: 0.20);
281 std::vector<Real> strikes = {0.01, 0.02};
282 std::vector<Date> dates = {refDate + 90, refDate + 180};
283 std::vector<std::vector<Handle<Quote> > > quotes = {
284 {Handle<Quote>(q), Handle<Quote>(q)},
285 {Handle<Quote>(q), Handle<Quote>(q)}
286 };
287
288 ext::shared_ptr<StrippedOptionletAdapter> vol =
289 ext::make_shared<StrippedOptionletAdapter>(args: ext::make_shared<StrippedOptionlet>(
290 args: 0, args: NullCalendar(), args: Unadjusted, args&: ibor, args&: dates, args&: strikes, args&: quotes, args: Actual365Fixed()));
291
292 Real v1 = vol->volatility(optionDate: refDate + 100, strike: 0.01);
293 q->setValue(0.21);
294 Real v2 = vol->volatility(optionDate: refDate + 100, strike: 0.01);
295 vol->update();
296 Real v3 = vol->volatility(optionDate: refDate + 100, strike: 0.01);
297 vol->deepUpdate();
298 Real v4 = vol->volatility(optionDate: refDate + 100, strike: 0.01);
299
300 QL_CHECK_CLOSE(v1, 0.2, 1E-10);
301 QL_CHECK_CLOSE(v2, 0.2, 1E-10);
302 QL_CHECK_CLOSE(v3, 0.2, 1E-10);
303 QL_CHECK_CLOSE(v4, 0.21, 1E-10);
304}
305
306namespace {
307 class DummyObserver : public Observer {
308 public:
309 DummyObserver() = default;
310 void update() override {}
311 };
312}
313
314void ObservableTest::testEmptyObserverList() {
315 BOOST_TEST_MESSAGE("Testing unregisterWith call on empty observer...");
316
317 const ext::shared_ptr<DummyObserver> dummyObserver=ext::make_shared<DummyObserver>();
318 dummyObserver->unregisterWith(h: ext::make_shared<SimpleQuote>(args: 10.0));
319}
320
321void ObservableTest::testAddAndDeleteObserverDuringNotifyObservers() {
322 BOOST_TEST_MESSAGE("Testing addition and deletion of observers during notifyObserver...");
323
324 const ext::shared_ptr<MersenneTwisterUniformRng> rng
325 = ext::make_shared<MersenneTwisterUniformRng>();
326
327 const Size nrInitialObserver = 20;
328 const Size nrDeleteDuringUpdate = 5;
329 const Size nrAdditionalObserver = 100;
330 const Size testRuns = 100;
331
332 class TestSetup {
333 public:
334 explicit TestSetup(ext::shared_ptr<MersenneTwisterUniformRng> m)
335 : rng(std::move(m)), observable(ext::make_shared<Observable>()) {}
336
337 ext::shared_ptr<MersenneTwisterUniformRng> rng;
338 ext::shared_ptr<Observable> observable;
339 std::vector<ext::shared_ptr<Observer> > expected;
340 std::vector<ext::shared_ptr<Observer> > additinalObservers;
341 };
342
343 class TestObserver: public Observer {
344 public:
345 explicit TestObserver(TestSetup* setup = nullptr) : setup_(setup) {}
346
347 void update() override {
348 ++updates_;
349
350 if (setup_ != nullptr) {
351 for (Size i=0; i < nrAdditionalObserver; ++i) {
352 const ext::shared_ptr<Observer> obs
353 = ext::make_shared<TestObserver>();
354
355 obs->registerWith(h: setup_->observable);
356 setup_->additinalObservers.push_back(x: obs);
357 }
358
359 for (Size i=0; i < nrDeleteDuringUpdate; ++i) {
360 const unsigned int j
361 = setup_->rng->nextInt32() % setup_->expected.size();
362
363 if (setup_->expected[j].get() != this)
364 setup_->expected.erase(position: setup_->expected.begin()+j);
365 }
366 }
367 }
368
369 Size getUpdates() const { return updates_; }
370
371 private:
372 TestSetup* const setup_;
373 Size updates_ = 0;
374 };
375
376 for (Size t=0; t < testRuns; ++t) {
377 const ext::shared_ptr<TestSetup> setup = ext::make_shared<TestSetup>(args: rng);
378
379 for (Size i=0; i < nrInitialObserver; ++i) {
380 const ext::shared_ptr<Observer> obs =
381 (i == nrInitialObserver/3 || i == nrInitialObserver/2)
382 ? ext::make_shared<TestObserver>(args: setup.get())
383 : ext::make_shared<TestObserver>();
384
385 obs->registerWith(h: setup->observable);
386 setup->expected.push_back(x: obs);
387 }
388
389 setup->observable->notifyObservers();
390
391 for (const auto& obs : setup->expected)
392 if (ext::dynamic_pointer_cast<TestObserver>(r: obs)->getUpdates() == 0) {
393 BOOST_FAIL("missed observer update detected");
394 }
395 }
396}
397
398
399test_suite* ObservableTest::suite() {
400 auto* suite = BOOST_TEST_SUITE("Observer tests");
401
402 suite->add(QUANTLIB_TEST_CASE(&ObservableTest::testObservableSettings));
403
404#ifdef QL_ENABLE_THREAD_SAFE_OBSERVER_PATTERN
405 suite->add(QUANTLIB_TEST_CASE(&ObservableTest::testAsyncGarbagCollector));
406 suite->add(QUANTLIB_TEST_CASE(
407 &ObservableTest::testMultiThreadingGlobalSettings));
408#endif
409
410 suite->add(QUANTLIB_TEST_CASE(&ObservableTest::testDeepUpdate));
411 suite->add(QUANTLIB_TEST_CASE(&ObservableTest::testEmptyObserverList));
412 suite->add(QUANTLIB_TEST_CASE(
413 &ObservableTest::testAddAndDeleteObserverDuringNotifyObservers));
414 return suite;
415}
416
417

source code of quantlib/test-suite/observable.cpp