[go: up one dir, main page]

1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 Copyright (C) 2023 Marcin Rybacki
4
5 This file is part of QuantLib, a free-software/open-source library
6 for financial quantitative analysts and developers - http://quantlib.org/
7
8 QuantLib is free software: you can redistribute it and/or modify it
9 under the terms of the QuantLib license. You should have received a
10 copy of the license along with this program; if not, please email
11 <quantlib-dev@lists.sf.net>. The license is also available online at
12 <http://quantlib.org/license.shtml>.
13
14 This program is distributed in the hope that it will be useful, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include "equityindex.hpp"
20#include "utilities.hpp"
21#include <ql/indexes/equityindex.hpp>
22#include <ql/time/calendars/target.hpp>
23#include <ql/quotes/simplequote.hpp>
24#include <string>
25
26using namespace QuantLib;
27using namespace boost::unit_test_framework;
28
29namespace equityindex_test {
30
31 struct CommonVars {
32
33 Date today;
34 Calendar calendar;
35 DayCounter dayCount;
36
37 ext::shared_ptr<EquityIndex> equityIndex;
38 RelinkableHandle<YieldTermStructure> interestHandle;
39 RelinkableHandle<YieldTermStructure> dividendHandle;
40 ext::shared_ptr<Quote> spot;
41 RelinkableHandle<Quote> spotHandle;
42
43 // utilities
44
45 CommonVars(bool addTodaysFixing = true) {
46 calendar = TARGET();
47 dayCount = Actual365Fixed();
48
49 equityIndex = ext::make_shared<EquityIndex>(args: "eqIndex", args&: calendar, args&: interestHandle,
50 args&: dividendHandle, args&: spotHandle);
51
52 today = calendar.adjust(Date(27, January, 2023));
53
54 if (addTodaysFixing)
55 equityIndex->addFixing(fixingDate: today, fixing: 8690.0);
56
57 Settings::instance().evaluationDate() = today;
58
59 interestHandle.linkTo(h: flatRate(forward: 0.03, dc: dayCount));
60 dividendHandle.linkTo(h: flatRate(forward: 0.01, dc: dayCount));
61
62 spot = ext::make_shared<SimpleQuote>(args: 8700.0);
63 spotHandle.linkTo(h: spot);
64 }
65 };
66}
67
68void EquityIndexTest::testTodaysFixing() {
69 BOOST_TEST_MESSAGE("Testing today's fixing...");
70
71 using namespace equityindex_test;
72
73 CommonVars vars;
74 const Real tolerance = 1.0e-8;
75
76 const Real historicalIndex = 8690.0;
77 Real todaysFixing = vars.equityIndex->fixing(fixingDate: vars.today);
78
79 if ((std::fabs(x: todaysFixing - historicalIndex) > tolerance))
80 BOOST_ERROR("today's fixing should be equal to historical index\n"
81 << " actual fixing: " << todaysFixing << "\n"
82 << " expected fixing: " << historicalIndex << "\n");
83
84 const Real spot = 8700.0;
85 Real forecastedFixing = vars.equityIndex->fixing(fixingDate: vars.today, forecastTodaysFixing: true);
86
87 if ((std::fabs(x: forecastedFixing - spot) > tolerance))
88 BOOST_ERROR("today's fixing forecast should be equal to spot\n"
89 << " actual forecast: " << forecastedFixing << "\n"
90 << " expected forecast: " << spot << "\n");
91}
92
93void EquityIndexTest::testTodaysFixingWithSpotAsProxy() {
94 BOOST_TEST_MESSAGE("Testing today's fixing with spot as proxy...");
95
96 using namespace equityindex_test;
97
98 CommonVars vars(false);
99 const Real tolerance = 1.0e-8;
100
101 const Real spot = 8700.0;
102 Real fixing = vars.equityIndex->fixing(fixingDate: vars.today);
103
104 if ((std::fabs(x: fixing - spot) > tolerance))
105 BOOST_ERROR("today's fixing should be equal to spot when historical index not added\n"
106 << " actual fixing: " << fixing << "\n"
107 << " expected fixing: " << spot << "\n");
108}
109
110void EquityIndexTest::testFixingForecast() {
111 BOOST_TEST_MESSAGE("Testing fixing forecast...");
112
113 using namespace equityindex_test;
114
115 CommonVars vars;
116 const Real tolerance = 1.0e-8;
117
118 Date forecastedDate(20, May, 2030);
119
120 Real forecast = vars.equityIndex->fixing(fixingDate: forecastedDate);
121 Real expectedForecast = vars.spotHandle->value() *
122 vars.dividendHandle->discount(d: forecastedDate) /
123 vars.interestHandle->discount(d: forecastedDate);
124
125 if ((std::fabs(x: forecast - expectedForecast) > tolerance))
126 BOOST_ERROR("could not replicate index forecast\n"
127 << " actual forecast: " << forecast << "\n"
128 << " expected forecast: " << expectedForecast << "\n");
129}
130
131void EquityIndexTest::testFixingForecastWithoutDividend() {
132 BOOST_TEST_MESSAGE("Testing fixing forecast without dividend...");
133
134 using namespace equityindex_test;
135
136 CommonVars vars;
137 const Real tolerance = 1.0e-8;
138
139 Date forecastedDate(20, May, 2030);
140
141 auto equityIndexExDiv =
142 vars.equityIndex->clone(interest: vars.interestHandle, dividend: Handle<YieldTermStructure>(), spot: vars.spotHandle);
143
144 Real forecast = equityIndexExDiv->fixing(fixingDate: forecastedDate);
145 Real expectedForecast =
146 vars.spotHandle->value() / vars.interestHandle->discount(d: forecastedDate);
147
148 if ((std::fabs(x: forecast - expectedForecast) > tolerance))
149 BOOST_ERROR("could not replicate index forecast without dividend\n"
150 << " actual forecast: " << forecast << "\n"
151 << " expected forecast: " << expectedForecast << "\n");
152}
153
154void EquityIndexTest::testFixingForecastWithoutSpot() {
155 BOOST_TEST_MESSAGE("Testing fixing forecast without spot handle...");
156
157 using namespace equityindex_test;
158
159 CommonVars vars;
160 const Real tolerance = 1.0e-8;
161
162 Date forecastedDate(20, May, 2030);
163
164 auto equityIndexExSpot =
165 vars.equityIndex->clone(interest: vars.interestHandle, dividend: vars.dividendHandle, spot: Handle<Quote>());
166
167 Real forecast = equityIndexExSpot->fixing(fixingDate: forecastedDate);
168 Real expectedForecast = equityIndexExSpot->pastFixing(fixingDate: vars.today) *
169 vars.dividendHandle->discount(d: forecastedDate) /
170 vars.interestHandle->discount(d: forecastedDate);
171
172 if ((std::fabs(x: forecast - expectedForecast) > tolerance))
173 BOOST_ERROR("could not replicate index forecast without spot handle\n"
174 << " actual forecast: " << forecast << "\n"
175 << " expected forecast: " << expectedForecast << "\n");
176}
177
178void EquityIndexTest::testFixingForecastWithoutSpotAndHistoricalFixing() {
179 BOOST_TEST_MESSAGE("Testing fixing forecast without spot handle and historical fixing...");
180
181 using namespace equityindex_test;
182
183 CommonVars vars(false);
184
185 Date forecastedDate(20, May, 2030);
186
187 auto equityIndexExSpot =
188 vars.equityIndex->clone(interest: vars.interestHandle, dividend: vars.dividendHandle, spot: Handle<Quote>());
189
190 BOOST_CHECK_EXCEPTION(
191 equityIndexExSpot->fixing(forecastedDate), Error,
192 ExpectedErrorMessage(
193 "Cannot forecast equity index, missing both spot and historical index"));
194}
195
196void EquityIndexTest::testSpotChange() {
197 BOOST_TEST_MESSAGE("Testing spot change...");
198
199 using namespace equityindex_test;
200
201 CommonVars vars;
202 const Real tolerance = 1.0e-8;
203
204 ext::shared_ptr<Quote> newSpot = ext::make_shared<SimpleQuote>(args: 9000.0);
205 vars.spotHandle.linkTo(h: newSpot);
206
207 if ((std::fabs(x: newSpot->value() - vars.equityIndex->spot()->value()) > tolerance))
208 BOOST_ERROR("could not re-link spot quote to new value\n"
209 << " actual spot: " << vars.equityIndex->spot()->value() << "\n"
210 << " expected spot: " << newSpot->value() << "\n");
211
212 vars.spotHandle.linkTo(h: vars.spot);
213
214 if ((std::fabs(x: vars.spot->value() - vars.equityIndex->spot()->value()) > tolerance))
215 BOOST_ERROR("could not re-link spot quote back to old value\n"
216 << " actual spot: " << vars.equityIndex->spot()->value() << "\n"
217 << " expected spot: " << vars.spot->value() << "\n");
218}
219
220void EquityIndexTest::testErrorWhenInvalidFixingDate() {
221 BOOST_TEST_MESSAGE("Testing error when invalid fixing date is used...");
222
223 using namespace equityindex_test;
224
225 CommonVars vars;
226
227 BOOST_CHECK_EXCEPTION(
228 vars.equityIndex->fixing(Date(1, January, 2023)), Error,
229 ExpectedErrorMessage("Fixing date January 1st, 2023 is not valid"));
230}
231
232void EquityIndexTest::testErrorWhenFixingMissing() {
233 BOOST_TEST_MESSAGE("Testing error when required fixing is missing...");
234
235 using namespace equityindex_test;
236
237 CommonVars vars;
238
239 BOOST_CHECK_EXCEPTION(
240 vars.equityIndex->fixing(Date(2, January, 2023)), Error,
241 ExpectedErrorMessage("Missing eqIndex fixing for January 2nd, 2023"));
242}
243
244void EquityIndexTest::testErrorWhenInterestHandleMissing() {
245 BOOST_TEST_MESSAGE("Testing error when interest handle is missing...");
246
247 using namespace equityindex_test;
248
249 CommonVars vars;
250
251 Date forecastedDate(20, May, 2030);
252
253 auto equityIndexExDiv =
254 vars.equityIndex->clone(
255 interest: Handle<YieldTermStructure>(), dividend: Handle<YieldTermStructure>(), spot: Handle<Quote>());
256
257 BOOST_CHECK_EXCEPTION(equityIndexExDiv->fixing(forecastedDate), Error,
258 ExpectedErrorMessage(
259 "null interest rate term structure set to this instance of eqIndex"));
260}
261
262void EquityIndexTest::testFixingObservability() {
263 BOOST_TEST_MESSAGE("Testing observability of index fixings...");
264
265 using namespace equityindex_test;
266
267 CommonVars vars;
268
269 ext::shared_ptr<EquityIndex> i1 =
270 ext::make_shared<EquityIndex>(args: "observableEquityIndex", args&: vars.calendar);
271
272 Flag flag;
273 flag.registerWith(h: i1);
274 flag.lower();
275
276 ext::shared_ptr<Index> i2 =
277 ext::make_shared<EquityIndex>(args: "observableEquityIndex", args&: vars.calendar);
278
279 i2->addFixing(fixingDate: vars.today, fixing: 100.0);
280 if (!flag.isUp())
281 BOOST_FAIL("Observer was not notified of added equity index fixing");
282}
283
284void EquityIndexTest::testNoErrorIfTodayIsNotBusinessDay() {
285 BOOST_TEST_MESSAGE("Testing that no error is thrown if today is not a business day...");
286
287 using namespace equityindex_test;
288
289 CommonVars vars;
290
291 Date today(28, January, 2023);
292 Date forecastedDate(20, May, 2030);
293
294 Settings::instance().evaluationDate() = today;
295
296 auto equityIndex =
297 vars.equityIndex->clone(interest: vars.interestHandle, dividend: vars.dividendHandle, spot: Handle<Quote>());
298
299 BOOST_REQUIRE_NO_THROW(vars.equityIndex->fixing(forecastedDate));
300}
301
302test_suite* EquityIndexTest::suite() {
303 auto* suite = BOOST_TEST_SUITE("Equity index tests");
304
305 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testTodaysFixing));
306 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testTodaysFixingWithSpotAsProxy));
307 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testFixingForecast));
308 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testFixingForecastWithoutDividend));
309 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testFixingForecastWithoutSpot));
310 suite->add(
311 QUANTLIB_TEST_CASE(&EquityIndexTest::testFixingForecastWithoutSpotAndHistoricalFixing));
312 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testSpotChange));
313 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testErrorWhenInvalidFixingDate));
314 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testErrorWhenFixingMissing));
315 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testErrorWhenInterestHandleMissing));
316 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testFixingObservability));
317 suite->add(QUANTLIB_TEST_CASE(&EquityIndexTest::testNoErrorIfTodayIsNotBusinessDay));
318
319 return suite;
320}
321

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