Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
sensitivityaggregator.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2018 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#include <boost/test/unit_test.hpp>
22#include <oret/toplevelfixture.hpp>
23#include <ql/math/comparison.hpp>
25
26using namespace boost::unit_test_framework;
27using namespace std;
28
33using std::function;
34using std::map;
35using std::set;
36
38
39// Sensitivity records for aggregation
40// clang-format off
41static const set<SensitivityRecord> records = {
42 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -103053.46, 74.06, 0.00},
43 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -103053.46, 354.79, -0.03 },
44 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -103053.46, -72.54, 0.00 },
45 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -103053.46, -347.52, 0.02 },
46 { "trade_001", false, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, RiskFactorKey(), "", 0.0, "USD", -103053.46, -50331.89, 0.00 },
47 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, "USD", -103053.46, 0, -0.01 },
48 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -103053.46, 0, 0.74 },
49 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -103053.46, 0, 3.55 },
50 { "trade_001", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, "USD", -103053.46, 0, 0.01 },
51 { "trade_002", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", 393612.36, 0.26, 0.00 },
52 { "trade_002", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", 393612.36, 14.11, 0.00 },
53 { "trade_002", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", 393612.36, -0.43, 0.00 },
54 { "trade_002", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", 393612.36, -23.32, 0.00 },
55 { "trade_002", false, RiskFactorKey(RFType::FXSpot, "TWDUSD", 0), "spot", 0.0002, RiskFactorKey(), "", 0.0, "USD", 393612.36, -6029.41, 0.00 },
56 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, 38.13, 0.00 },
57 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, 114.53, 0.00 },
58 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, -37.48, 0.00 },
59 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, -112.57, 0.00 },
60 { "trade_003", false, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, RiskFactorKey(), "", 0.0, "USD", -156337.99, -91345.92, 0.00 },
61 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, "USD", -156337.99, 0, 0.00 },
62 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -156337.99, 0, 0.38 },
63 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -156337.99, 0, 1.15 },
64 { "trade_003", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, "USD", -156337.99, 0, 0.00 },
65 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -110809.22, 27.11, 0.00 },
66 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -110809.22, 940.54, -0.09 },
67 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -110809.22, -26.81, 0.00 },
68 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -110809.22, -930.06, 0.09 },
69 { "trade_004", false, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, RiskFactorKey(), "", 0.0, "USD", -110809.22, -99495.14, 0.00 },
70 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, "USD", -110809.22, 0, 0.00 },
71 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -110809.22, 0, 0.27 },
72 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -110809.22, 0, 9.41 },
73 { "trade_004", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, "USD", -110809.22, 0, 0.00 },
74 { "trade_005", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 393612.36, 0.26, 0.00 },
75 { "trade_005", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 393612.36, 14.11, 0.00 },
76 { "trade_005", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 393612.36, -0.43, 0.00 },
77 { "trade_005", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 393612.36, -23.32, 0.00 },
78 { "trade_005", false, RiskFactorKey(RFType::FXSpot, "TWDUSD", 0), "spot", 0.0002, RiskFactorKey(), "", 0.0, "EUR", 393612.36, -6029.41, 0.00 },
79 { "trade_006", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", -393612.36, -0.26, 0.00 },
80 { "trade_006", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", -393612.36, -14.11, 0.00 },
81 { "trade_006", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", -393612.36, 0.43, 0.00 },
82 { "trade_006", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", -393612.36, 23.32, 0.00 },
83 { "trade_006", false, RiskFactorKey(RFType::FXSpot, "TWDUSD", 0), "spot", 0.0002, RiskFactorKey(), "", 0.0, "EUR", -393612.36, 6029.41, 0.00 }
84};
85// clang-format on
86
87// Expected result of aggregation over all elements above
88// clang-format off
89set<SensitivityRecord> expAggregationAll = {
90 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, 38.13, 0 },
91 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, 114.53, 0 },
92 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -213862.68, 101.17, 0 },
93 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -213862.68, 1295.33, -0.12 },
94 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, -37.48, 0 },
95 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -156337.99, -112.57, 0 },
96 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(), "", 0.0, "USD", -213862.68, -99.35, 0 },
97 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, RiskFactorKey(), "", 0.0, "USD", -213862.68, -1277.58, 0.11 },
98 { "", false, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, RiskFactorKey(), "", 0.0, "USD", -370200.67, -241172.95, 0 },
99 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, "USD", -156337.99, 0.00, 0.00 },
100 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 1), "1M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -156337.99, 0.00, 0.38 },
101 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 2), "3M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -156337.99, 0.00, 1.15 },
102 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, "USD", -213862.68, 0.00, -0.01 },
103 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 3), "6M", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -213862.68, 0.00, 1.01 },
104 { "", false, RiskFactorKey(RFType::DiscountCurve, "CNY", 4), "1Y", 0.0001, RiskFactorKey(RFType::FXSpot, "CNYUSD", 0), "spot", 0.001534, "USD", -213862.68, 0.00, 12.96 },
105 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, "USD", -156337.99, 0.00, 0.00 },
106 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 3), "6M", 0.0001, RiskFactorKey(RFType::DiscountCurve, "USD", 4), "1Y", 0.0001, "USD", -213862.68, 0.00, 0.01 },
107 { "", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 0, 0, 0.00 },
108 { "", false, RiskFactorKey(RFType::DiscountCurve, "TWD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 0, 0, 0.00 },
109 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 1), "1M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 0, 0, 0.00 },
110 { "", false, RiskFactorKey(RFType::DiscountCurve, "USD", 2), "3M", 0.0001, RiskFactorKey(), "", 0.0, "EUR", 0, 0, 0.00 },
111 { "", false, RiskFactorKey(RFType::FXSpot, "TWDUSD", 0), "spot", 0.0002, RiskFactorKey(), "", 0.0, "EUR", 0, 0, 0.00 }
112};
113// clang-format on
114
115// Utility function to filter records by trade ID
116// The aggregated results have trade ID = "" so do that here for comparison
117set<SensitivityRecord> filter(set<SensitivityRecord> records, const string& tradeId) {
118 set<SensitivityRecord> res;
119 for (auto sr : records) {
120 if (sr.tradeId == tradeId) {
121 sr.tradeId = "";
122 res.insert(sr);
123 }
124 }
125
126 return res;
127}
128
129// Check the expected result, 'exp', against the actual result, 'res'.
130void check(const set<SensitivityRecord>& exp, const set<SensitivityRecord>& res, const string& category) {
131 BOOST_CHECK_EQUAL_COLLECTIONS(exp.begin(), exp.end(), res.begin(), res.end());
132 for (set<SensitivityRecord>::iterator itExp = exp.begin(), itRes = res.begin(); itExp != exp.end();
133 itExp++, itRes++) {
134 BOOST_CHECK_MESSAGE(QuantLib::close(itExp->baseNpv, itRes->baseNpv),
135 "Category = " << category << ": Base NPVs (exp = " << itExp->baseNpv
136 << " vs. actual = " << itRes->baseNpv
137 << ") are not equal for aggregated expected record " << *itExp);
138 BOOST_CHECK_MESSAGE(QuantLib::close(itExp->delta, itRes->delta),
139 "Category = " << category << ": Deltas (exp = " << itExp->delta
140 << " vs. actual = " << itRes->delta
141 << ") are not equal for aggregated expected record " << *itExp);
142 BOOST_CHECK_MESSAGE(QuantLib::close(itExp->gamma, itRes->gamma),
143 "Category = " << category << ": Gammas (exp = " << itExp->gamma
144 << " vs. actual = " << itRes->gamma
145 << ") are not equal for aggregated expected record " << *itExp);
146 }
147}
148
149BOOST_FIXTURE_TEST_SUITE(OREAnalyticsTestSuite, ore::test::OreaTopLevelFixture)
150
151BOOST_AUTO_TEST_SUITE(SensitivityAggregatorTest)
152
153BOOST_AUTO_TEST_CASE(testGeneralAggregationSetCategories) {
154
155 BOOST_TEST_MESSAGE("Testing general aggregation using sets of trades for categories");
156
157 // Streamer
158 SensitivityInMemoryStream ss(records.begin(), records.end());
159
160 // Categories for aggregator
161 map<string, set<std::pair<std::string, QuantLib::Size>>> categories;
162 // No aggregation, just single trade categories
163 set<pair<string, QuantLib::Size>> trades = {make_pair("trade_001", 0), make_pair("trade_003", 1),
164 make_pair("trade_004", 2), make_pair("trade_005", 3),
165 make_pair("trade_006", 4)};
166
167 for (const auto& trade : trades) {
168 categories[trade.first] = {trade};
169 }
170 // Aggregate over all trades except trade_002
171 categories["all_except_002"] = trades;
172
173 // Create aggregator and call aggregate
174 SensitivityAggregator sAgg(categories);
175 sAgg.aggregate(ss);
176
177 // Containers for expected and actual results respectively below
178 set<SensitivityRecord> exp;
179 set<SensitivityRecord> res;
180
181 // Test results for single trade categories
182 for (const auto& trade : trades) {
183 exp = filter(records, trade.first);
184 res = sAgg.sensitivities(trade.first);
185 BOOST_TEST_MESSAGE("Testing for category with single trade " << trade.first);
186 check(exp, res, trade.first);
187 }
188
189 // Test results for the aggregated "All" category
190 BOOST_TEST_MESSAGE("Testing for category 'all_except_002'");
191 res = sAgg.sensitivities("all_except_002");
192 check(expAggregationAll, res, "all_except_002");
193}
194
195BOOST_AUTO_TEST_CASE(testGeneralAggregationFunctionCategories) {
196
197 BOOST_TEST_MESSAGE("Testing general aggregation using functions for categories");
198
199 // Streamer
200 SensitivityInMemoryStream ss(records.begin(), records.end());
201
202 // Category functions for aggregator
203 map<string, function<bool(string)>> categories;
204 // No aggregation, just single trade categories
205 set<pair<string, QuantLib::Size>> trades = {make_pair("trade_001", 0), make_pair("trade_003", 1),
206 make_pair("trade_004", 2), make_pair("trade_005", 3),
207 make_pair("trade_006", 4)};
208
209 for (const auto& trade : trades) {
210 categories[trade.first] = [&trade](string tradeId) { return tradeId == trade.first; };
211 }
212 // Aggregate over all trades except trade_002
213 categories["all_except_002"] = [&trades](string tradeId) {
214 for (auto it = trades.begin(); it != trades.end(); ++it) {
215 if (it->first == tradeId)
216 return true;
217 }
218 return false;
219 };
220
221 // Create aggregator and call aggregate
222 SensitivityAggregator sAgg(categories);
223 sAgg.aggregate(ss);
224
225 // Containers for expected and actual results respectively below
226 set<SensitivityRecord> exp;
227 set<SensitivityRecord> res;
228
229 // Test results for single trade categories
230 for (const auto& trade : trades) {
231 exp = filter(records, trade.first);
232 res = sAgg.sensitivities(trade.first);
233 BOOST_TEST_MESSAGE("Testing for category with single trade " << trade.first);
234 check(exp, res, trade.first);
235 }
236
237 // Test results for the aggregated "All" category
238 BOOST_TEST_MESSAGE("Testing for category 'all_except_002'");
239 res = sAgg.sensitivities("all_except_002");
240 check(expAggregationAll, res, "all_except_002");
241}
242
243BOOST_AUTO_TEST_SUITE_END()
244
245BOOST_AUTO_TEST_SUITE_END()
Data types stored in the scenario class.
Definition: scenario.hpp:48
KeyType
Risk Factor types.
Definition: scenario.hpp:51
const std::set< SensitivityRecord > & sensitivities(const std::string &category) const
void aggregate(SensitivityStream &ss, const QuantLib::ext::shared_ptr< ScenarioFilter > &filter=QuantLib::ext::make_shared< ScenarioFilter >())
Class for streaming SensitivityRecords from csv file.
OREAnalytics Top level fixture.
SafeStack< Filter > filter
RandomVariable exp(RandomVariable x)
Fixture that can be used at top level of OREAnalytics test suites.
Class for aggregating SensitivityRecords.
Class for streaming SensitivityRecords from in-memory container.
BOOST_AUTO_TEST_CASE(testGeneralAggregationSetCategories)
set< SensitivityRecord > expAggregationAll