[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 "equitycashflow.hpp"
20#include "utilities.hpp"
21#include <ql/cashflows/equitycashflow.hpp>
22#include <ql/indexes/equityindex.hpp>
23#include <ql/time/calendars/target.hpp>
24#include <ql/quotes/simplequote.hpp>
25
26using namespace QuantLib;
27using namespace boost::unit_test_framework;
28
29namespace equitycashflow_test {
30
31 struct CommonVars {
32
33 Date today;
34 Calendar calendar;
35 DayCounter dayCount;
36
37 Real notional;
38
39 ext::shared_ptr<EquityIndex> equityIndex;
40
41 RelinkableHandle<YieldTermStructure> localCcyInterestHandle;
42 RelinkableHandle<YieldTermStructure> dividendHandle;
43 RelinkableHandle<YieldTermStructure> quantoCcyInterestHandle;
44
45 RelinkableHandle<BlackVolTermStructure> equityVolHandle;
46 RelinkableHandle<BlackVolTermStructure> fxVolHandle;
47
48 RelinkableHandle<Quote> spotHandle;
49 RelinkableHandle<Quote> correlationHandle;
50
51 // utilities
52
53 CommonVars() {
54 calendar = TARGET();
55 dayCount = Actual365Fixed();
56 notional = 1.0e7;
57
58 today = calendar.adjust(Date(27, January, 2023));
59 Settings::instance().evaluationDate() = today;
60
61 equityIndex = ext::make_shared<EquityIndex>(args: "eqIndex", args&: calendar, args&: localCcyInterestHandle,
62 args&: dividendHandle, args&: spotHandle);
63 equityIndex->addFixing(fixingDate: Date(5, January, 2023), fixing: 9010.0);
64 equityIndex->addFixing(fixingDate: today, fixing: 8690.0);
65
66 localCcyInterestHandle.linkTo(h: flatRate(forward: 0.0375, dc: dayCount));
67 dividendHandle.linkTo(h: flatRate(forward: 0.005, dc: dayCount));
68 quantoCcyInterestHandle.linkTo(h: flatRate(forward: 0.001, dc: dayCount));
69
70 equityVolHandle.linkTo(h: flatVol(volatility: 0.4, dc: dayCount));
71 fxVolHandle.linkTo(h: flatVol(volatility: 0.2, dc: dayCount));
72
73 spotHandle.linkTo(h: ext::make_shared<SimpleQuote>(args: 8700.0));
74 correlationHandle.linkTo(h: ext::make_shared<SimpleQuote>(args: 0.4));
75 }
76
77 ext::shared_ptr<EquityCashFlow>
78 createEquityQuantoCashFlow(const ext::shared_ptr<EquityIndex>& index,
79 const Date& start,
80 const Date& end,
81 bool useQuantoPricer = true) {
82
83 auto cf = ext::make_shared<EquityCashFlow>(args&: notional, args: index, args: start, args: end, args: end);
84 if (useQuantoPricer) {
85 auto pricer = ext::make_shared<EquityQuantoCashFlowPricer>(
86 args&: quantoCcyInterestHandle, args&: equityVolHandle, args&: fxVolHandle, args&: correlationHandle);
87 cf->setPricer(pricer);
88 }
89 return cf;
90 }
91
92 ext::shared_ptr<EquityCashFlow>
93 createEquityQuantoCashFlow(const ext::shared_ptr<EquityIndex>& index,
94 bool useQuantoPricer = true) {
95 Date start(5, January, 2023);
96 Date end(5, April, 2023);
97
98 return createEquityQuantoCashFlow(index, start, end, useQuantoPricer);
99 }
100
101 ext::shared_ptr<EquityCashFlow> createEquityQuantoCashFlow(bool useQuantoPricer = true) {
102 return createEquityQuantoCashFlow(index: equityIndex, useQuantoPricer);
103 }
104 };
105
106 void bumpMarketData(CommonVars& vars) {
107
108 vars.localCcyInterestHandle.linkTo(h: flatRate(forward: 0.04, dc: vars.dayCount));
109 vars.dividendHandle.linkTo(h: flatRate(forward: 0.01, dc: vars.dayCount));
110 vars.quantoCcyInterestHandle.linkTo(h: flatRate(forward: 0.03, dc: vars.dayCount));
111
112 vars.equityVolHandle.linkTo(h: flatVol(volatility: 0.45, dc: vars.dayCount));
113 vars.fxVolHandle.linkTo(h: flatVol(volatility: 0.25, dc: vars.dayCount));
114
115 vars.spotHandle.linkTo(h: ext::make_shared<SimpleQuote>(args: 8710.0));
116 }
117
118 void checkQuantoCorrection(bool includeDividend, bool bumpData = false) {
119 const Real tolerance = 1.0e-6;
120
121 CommonVars vars;
122
123 ext::shared_ptr<EquityIndex> equityIndex =
124 includeDividend ?
125 vars.equityIndex :
126 vars.equityIndex->clone(interest: vars.localCcyInterestHandle, dividend: Handle<YieldTermStructure>(),
127 spot: vars.spotHandle);
128
129 auto cf = vars.createEquityQuantoCashFlow(index: equityIndex);
130
131 if (bumpData)
132 bumpMarketData(vars);
133
134 Real strike = vars.equityIndex->fixing(fixingDate: cf->fixingDate());
135 Real indexStart = vars.equityIndex->fixing(fixingDate: cf->baseDate());
136
137 Real time = vars.localCcyInterestHandle->timeFromReference(d: cf->fixingDate());
138 Real rf = vars.localCcyInterestHandle->zeroRate(t: time, comp: Continuous);
139 Real q = includeDividend ? vars.dividendHandle->zeroRate(t: time, comp: Continuous) : Real(0.0);
140 Real eqVol = vars.equityVolHandle->blackVol(d: cf->fixingDate(), strike);
141 Real fxVol = vars.fxVolHandle->blackVol(d: cf->fixingDate(), strike: 1.0);
142 Real rho = vars.correlationHandle->value();
143 Real spot = vars.spotHandle->value();
144
145 Real quantoForward = spot * std::exp(x: (rf - q - rho * eqVol * fxVol) * time);
146 Real expectedAmount = (quantoForward / indexStart - 1.0) * vars.notional;
147
148 Real actualAmount = cf->amount();
149
150 if ((std::fabs(x: actualAmount - expectedAmount) > tolerance))
151 BOOST_ERROR("could not replicate equity quanto correction\n"
152 << " actual amount: " << actualAmount << "\n"
153 << " expected amount: " << expectedAmount << "\n"
154 << " index start: " << indexStart << "\n"
155 << " index end: " << quantoForward << "\n"
156 << " local rate: " << rf << "\n"
157 << " equity volatility: " << eqVol << "\n"
158 << " FX volatility: " << fxVol << "\n"
159 << " correlation: " << rho << "\n"
160 << " spot: " << spot << "\n");
161 }
162
163 void checkRaisedError(const ext::shared_ptr<EquityCashFlow>& cf, const std::string& message) {
164 BOOST_CHECK_EXCEPTION(cf->amount(), Error, ExpectedErrorMessage(message));
165 }
166}
167
168void EquityCashFlowTest::testSimpleEquityCashFlow() {
169 BOOST_TEST_MESSAGE("Testing simple equity cash flow...");
170
171 using namespace equitycashflow_test;
172
173 const Real tolerance = 1.0e-6;
174
175 CommonVars vars;
176
177 auto cf = vars.createEquityQuantoCashFlow(useQuantoPricer: false);
178
179 Real indexStart = vars.equityIndex->fixing(fixingDate: cf->baseDate());
180 Real indexEnd = vars.equityIndex->fixing(fixingDate: cf->fixingDate());
181
182 Real expectedAmount = (indexEnd / indexStart - 1.0) * vars.notional;
183
184 Real actualAmount = cf->amount();
185
186 if ((std::fabs(x: actualAmount - expectedAmount) > tolerance))
187 BOOST_ERROR("could not replicate simple equity quanto cash flow\n"
188 << " actual amount: " << actualAmount << "\n"
189 << " expected amount: " << expectedAmount << "\n"
190 << " index start: " << indexStart << "\n"
191 << " index end: " << indexEnd << "\n");
192}
193
194void EquityCashFlowTest::testQuantoCorrection() {
195 BOOST_TEST_MESSAGE("Testing quanto correction...");
196
197 using namespace equitycashflow_test;
198
199 checkQuantoCorrection(includeDividend: true);
200 checkQuantoCorrection(includeDividend: false);
201
202 // Checks whether observers are being notified
203 // about changes in market data handles.
204 checkQuantoCorrection(includeDividend: false, bumpData: true);
205}
206
207void EquityCashFlowTest::testErrorWhenBaseDateAfterFixingDate() {
208 BOOST_TEST_MESSAGE("Testing error when base date after fixing date...");
209
210 using namespace equitycashflow_test;
211
212 CommonVars vars;
213
214 Date end(5, January, 2023);
215 Date start(5, April, 2023);
216
217 auto cf = vars.createEquityQuantoCashFlow(index: vars.equityIndex, start, end);
218
219 checkRaisedError(cf, message: "Fixing date cannot fall before base date.");
220}
221
222void EquityCashFlowTest::testErrorWhenQuantoCurveHandleIsEmpty() {
223 BOOST_TEST_MESSAGE("Testing error when quanto currency curve handle is empty...");
224
225 using namespace equitycashflow_test;
226
227 CommonVars vars;
228
229 auto cf = vars.createEquityQuantoCashFlow();
230
231 ext::shared_ptr<YieldTermStructure> yts;
232 vars.quantoCcyInterestHandle.linkTo(h: yts);
233 checkRaisedError(cf, message: "Quanto currency term structure handle cannot be empty.");
234}
235
236void EquityCashFlowTest::testErrorWhenEquityVolHandleIsEmpty() {
237 BOOST_TEST_MESSAGE("Testing error when equity vol handle is empty...");
238
239 using namespace equitycashflow_test;
240
241 CommonVars vars;
242
243 auto cf = vars.createEquityQuantoCashFlow();
244
245 ext::shared_ptr<BlackVolTermStructure> vol;
246 vars.equityVolHandle.linkTo(h: vol);
247 checkRaisedError(cf, message: "Equity volatility term structure handle cannot be empty.");
248}
249
250void EquityCashFlowTest::testErrorWhenFXVolHandleIsEmpty() {
251 BOOST_TEST_MESSAGE("Testing error when FX vol handle is empty...");
252
253 using namespace equitycashflow_test;
254
255 CommonVars vars;
256
257 auto cf = vars.createEquityQuantoCashFlow();
258
259 ext::shared_ptr<BlackVolTermStructure> vol;
260 vars.fxVolHandle.linkTo(h: vol);
261 checkRaisedError(cf, message: "FX volatility term structure handle cannot be empty.");
262}
263
264void EquityCashFlowTest::testErrorWhenCorrelationHandleIsEmpty() {
265 BOOST_TEST_MESSAGE("Testing error when correlation handle is empty...");
266
267 using namespace equitycashflow_test;
268
269 CommonVars vars;
270
271 auto cf = vars.createEquityQuantoCashFlow();
272
273 ext::shared_ptr<Quote> correlation;
274 vars.correlationHandle.linkTo(h: correlation);
275 checkRaisedError(cf, message: "Correlation handle cannot be empty.");
276}
277
278void EquityCashFlowTest::testErrorWhenInconsistentMarketDataReferenceDate() {
279 BOOST_TEST_MESSAGE("Testing error when market data reference dates are inconsistent...");
280
281 using namespace equitycashflow_test;
282
283 CommonVars vars;
284
285 auto cf = vars.createEquityQuantoCashFlow();
286
287 vars.quantoCcyInterestHandle.linkTo(h: flatRate(today: Date(26, January, 2023), forward: 0.02, dc: vars.dayCount));
288
289 checkRaisedError(
290 cf, message: "Quanto currency term structure, equity and FX volatility need to have the same "
291 "reference date.");
292}
293
294test_suite* EquityCashFlowTest::suite() {
295 auto* suite = BOOST_TEST_SUITE("Equity cash flow tests");
296
297 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testSimpleEquityCashFlow));
298 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testQuantoCorrection));
299 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenBaseDateAfterFixingDate));
300 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenQuantoCurveHandleIsEmpty));
301 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenEquityVolHandleIsEmpty));
302 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenFXVolHandleIsEmpty));
303 suite->add(QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenCorrelationHandleIsEmpty));
304 suite->add(
305 QUANTLIB_TEST_CASE(&EquityCashFlowTest::testErrorWhenInconsistentMarketDataReferenceDate));
306
307 return suite;
308}
309

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