Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
fxtriangulation.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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
25
27
28#include <ql/errors.hpp>
29#include <ql/quotes/compositequote.hpp>
30#include <ql/quotes/derivedquote.hpp>
31#include <ql/quotes/simplequote.hpp>
32
33#include <boost/make_shared.hpp>
34
35using namespace QuantLib;
36using namespace QuantExt;
37
38namespace ore {
39namespace data {
40
41namespace {
42
43std::pair<std::string, std::string> splitPair(const std::string& pair) {
44 QL_REQUIRE(pair.size() == 6, "FXTriangulation: Invalid currency pair '" << pair << "'");
45 return std::make_pair(pair.substr(0, 3), pair.substr(3));
46}
47
48Handle<YieldTermStructure> getMarketDiscountCurve(const Market* market, const std::string& ccy,
49 const std::string& configuration) {
50 try {
51 return market->discountCurve(ccy, configuration);
52 } catch (const std::exception&) {
53 WLOG("FXTriangulation: could not get market discount curve '"
54 << ccy << "' (requested for configuration '" << configuration
55 << "') - discounted fx spot rates will be replaced by non-discounted rates in future calculations, which "
56 "might lead to inaccuracies");
57 return Handle<YieldTermStructure>();
58 }
59}
60
61} // namespace
62
63FXTriangulation::FXTriangulation(std::map<std::string, Handle<Quote>> quotes) : quotes_(std::move(quotes)) {
64
65 LOG("FXTriangulation: initializing");
66
67 // collect all currencies from the pairs
68
69 std::set<std::string> ccys;
70 for (auto const& q : quotes_) {
71 auto [ccy1, ccy2] = splitPair(q.first);
72 ccys.insert(ccy1);
73 ccys.insert(ccy2);
74 TLOG("FXTriangulation: adding quote " << q.first);
75 }
76
77 /* - populate node to ccy vector
78 - we insert currencies in the order we want to use them for triangulation if there
79 are several shortest paths from CCY1 to CCY2 */
80
81 static vector<string> ccyOrder = {"USD", "EUR", "GBP", "CHF", "JPY", "AUD", "CAD", "ZAR"};
82
83 std::set<std::string> remainingCcys(ccys);
84
85 for (auto const& c : ccyOrder) {
86 if (ccys.find(c) != ccys.end()) {
87 nodeToCcy_.push_back(c);
88 remainingCcys.erase(c);
89 }
90 }
91
92 for (auto const& c : remainingCcys) {
93 nodeToCcy_.push_back(c);
94 }
95
96 // populate ccy to node vector
97
98 for (Size i = 0; i < nodeToCcy_.size(); ++i)
99 ccyToNode_[nodeToCcy_[i]] = i;
100
101 // populate neighbours container
102
103 neighbours_.resize(nodeToCcy_.size());
104 for (auto const& q : quotes_) {
105 auto [ccy1, ccy2] = splitPair(q.first);
106 Size n1 = ccyToNode_[ccy1];
107 Size n2 = ccyToNode_[ccy2];
108 neighbours_[n1].insert(n2);
109 neighbours_[n2].insert(n1);
110 }
111
112 LOG("FXTriangulation: initialized with " << quotes_.size() << " quotes, " << ccys.size() << " currencies.");
113}
114
115Handle<Quote> FXTriangulation::getQuote(const std::string& pair) const {
116
117 // do we have a cached result?
118
119 if (auto it = quoteCache_.find(pair); it != quoteCache_.end())
120 return it->second;
121
122 // we need to construct the quote from the input quotes
123
124 Handle<Quote> result;
125 auto [ccy1, ccy2] = splitPair(pair);
126
127 // handle trivial case
128
129 if (ccy1 == ccy2)
130 return Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(1.0));
131
132 // get the path from ccy1 to ccy2
133
134 auto path = getPath(ccy1, ccy2);
135
136 if (path.size() == 2) {
137
138 // we can use a direct or inverted quote, but do not need a composite
139
140 result = getQuote(path[0], path[1]);
141
142 } else {
143
144 // we need a composite quote
145
146 std::vector<Handle<Quote>> quotes;
147
148 // collect the quotes on the path and check consistency of the settlement dates
149
150 for (Size i = 0; i < path.size() - 1; ++i) {
151 quotes.push_back(getQuote(path[i], path[i + 1]));
152 }
153
154 // build the composite quote
155
156 auto f = [](const std::vector<Real>& quotes) {
157 return std::accumulate(quotes.begin(), quotes.end(), 1.0, std::multiplies());
158 };
159 result = Handle<Quote>(QuantLib::ext::make_shared<CompositeVectorQuote<decltype(f)>>(quotes, f));
160 }
161
162 // add the result to the lookup cache and return it
163
164 quoteCache_[pair] = result;
165 return result;
166}
167
168Handle<FxIndex> FXTriangulation::getIndex(const std::string& indexOrPair, const Market* market,
169 const std::string& configuration) const {
170
171 // do we have a cached result?
172
173 if (auto it = indexCache_.find(std::make_pair(indexOrPair, configuration)); it != indexCache_.end()) {
174 return it->second;
175 }
176
177 // otherwise we need to construct the index
178
179 Handle<FxIndex> result;
180
181 std::string familyName;
182 std::string forCcy;
183 std::string domCcy;
184
185 if (isFxIndex(indexOrPair)) {
186 auto ind = parseFxIndex(indexOrPair);
187 familyName = ind->familyName();
188 forCcy = ind->sourceCurrency().code();
189 domCcy = ind->targetCurrency().code();
190 } else {
191 familyName = "GENERIC";
192 std::tie(forCcy, domCcy) = splitPair(indexOrPair);
193 }
194
195 // get the conventions of the result index
196
197 auto [fixingDays, fixingCalendar, bdc] = getFxIndexConventions(indexOrPair);
198
199 // get the discount curves for the result index
200
201 auto sourceYts = getMarketDiscountCurve(market, forCcy, configuration);
202 auto targetYts = getMarketDiscountCurve(market, domCcy, configuration);
203
204 // get the path from ccy1 to ccy2
205
206 auto path = getPath(forCcy, domCcy);
207
208 if (path.size() == 2) {
209
210 // we can use a direct or inverted quote, but do not need a composite
211
212 auto fxSpot = getQuote(path[0], path[1]);
213 result = Handle<FxIndex>(QuantLib::ext::make_shared<FxIndex>(familyName, fixingDays, parseCurrency(forCcy),
214 parseCurrency(domCcy), fixingCalendar, fxSpot, sourceYts,
215 targetYts));
216
217 } else {
218
219 // we need a composite quote
220
221 std::vector<Handle<Quote>> quotes;
222
223 // collect the quotes on the path and store them as FxRate quotes ("as of today" - quotes)
224
225 for (Size i = 0; i < path.size() - 1; ++i) {
226
227 auto q = getQuote(path[i], path[i + 1]);
228
229 // we store a quote "as of today" to account for possible spot lag differences
230
231 auto [fd, fc, bdc] = getFxIndexConventions(path[i] + path[i + 1]);
232 auto s_yts = getMarketDiscountCurve(market, path[i], configuration);
233 auto t_yts = getMarketDiscountCurve(market, path[i + 1], configuration);
234 quotes.push_back(Handle<Quote>(QuantLib::ext::make_shared<FxRateQuote>(q, s_yts, t_yts, fd, fc)));
235 }
236
237 // build the composite quote "as of today"
238
239 auto f = [](const std::vector<Real>& quotes) {
240 return std::accumulate(quotes.begin(), quotes.end(), 1.0, std::multiplies());
241 };
242 Handle<Quote> compQuote(QuantLib::ext::make_shared<CompositeVectorQuote<decltype(f)>>(quotes, f));
243
244 // build the spot quote
245
246 Handle<Quote> spotQuote(
247 QuantLib::ext::make_shared<FxSpotQuote>(compQuote, sourceYts, targetYts, fixingDays, fixingCalendar));
248
249 // build the idnex
250
251 result = Handle<FxIndex>(QuantLib::ext::make_shared<FxIndex>(familyName, fixingDays, parseCurrency(forCcy),
252 parseCurrency(domCcy), fixingCalendar, spotQuote,
253 sourceYts, targetYts));
254 }
255
256 // add the result to the lookup cache and return it
257
258 indexCache_[std::make_pair(indexOrPair, configuration)] = result;
259 return result;
260}
261
262std::vector<std::string> FXTriangulation::getPath(const std::string& forCcy, const std::string& domCcy) const {
263
264 // see https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
265
266 Size sourceNode, targetNode;
267
268 if (auto it = ccyToNode_.find(forCcy); it != ccyToNode_.end()) {
269 sourceNode = it->second;
270 } else {
271 QL_FAIL("FXTriangulation: no conversion from '"
272 << forCcy << "' to '" << domCcy << "' possible, since '" << forCcy
273 << "' is not available as one of the currencies in any of the quotes (" << getAllQuotes() << ")");
274 }
275
276 if (auto it = ccyToNode_.find(domCcy); it != ccyToNode_.end()) {
277 targetNode = it->second;
278 } else {
279 QL_FAIL("FXTriangulation: no conversion from '"
280 << forCcy << "' to '" << domCcy << "' possible, since '" << domCcy
281 << "' is not available as one of the currencies in any of the quotes (" << getAllQuotes() << ")");
282 }
283
284 // previous node on the current shortest path
285 std::vector<Size> prev(nodeToCcy_.size(), Null<Size>());
286 // distance per node
287 std::vector<Size> dist(nodeToCcy_.size(), std::numeric_limits<Size>::max());
288 // visited flag
289 std::vector<bool> visited(nodeToCcy_.size(), false);
290
291 // init source
292 dist[sourceNode] = 0;
293
294 // main loop
295 Size noVisited = 0;
296 while (noVisited < nodeToCcy_.size()) {
297 Size u = Null<Size>(), min = std::numeric_limits<Size>::max();
298 for (Size i = 0; i < dist.size(); ++i) {
299 if (!visited[i] && dist[i] < min) {
300 u = i;
301 min = dist[i];
302 }
303 }
304 QL_REQUIRE(u != Null<Size>(), "FXTriangulation: internal error, no minimum found in dist array for '"
305 << forCcy << "' to '" << domCcy << "'. Quotes = " << getAllQuotes());
306 if (u == targetNode)
307 break;
308 visited[u] = true;
309 ++noVisited;
310 for (auto const& n : neighbours_[u]) {
311 if (visited[n])
312 continue;
313 Size alt = dist[u] + 1;
314 if (alt < dist[n]) {
315 dist[n] = alt;
316 prev[n] = u;
317 }
318 }
319 }
320
321 // did we find a path?
322 if (dist[targetNode] < std::numeric_limits<Size>::max()) {
323 std::vector<std::string> result;
324 Size u = targetNode;
325 while (u != sourceNode) {
326 result.insert(result.begin(), nodeToCcy_[u]);
327 u = prev[u];
328 QL_REQUIRE(u != Null<Size>(), "FXTriangulation: internal error u == null for '"
329 << forCcy << "' to '" << domCcy
330 << "'. Contact dev. Quotes = " << getAllQuotes() << ".");
331 }
332 result.insert(result.begin(), nodeToCcy_[sourceNode]);
333 TLOG("FXTriangulation: found path of length "
334 << result.size() - 1 << " from '" << forCcy << "' to '" << domCcy << "': "
335 << std::accumulate(
336 result.begin(), result.end(), std::string(),
337 [](const std::string& s1, const std::string& s2) { return s1.empty() ? s2 : s1 + "-" + s2; }));
338 return result;
339 }
340
341 QL_FAIL("FXTriangulation: no path from '" << forCcy << "' to '" << domCcy
342 << "' found. Quotes = " << getAllQuotes());
343}
344
345Handle<Quote> FXTriangulation::getQuote(const std::string& forCcy, const std::string& domCcy) const {
346 if (auto it = quotes_.find(forCcy + domCcy); it != quotes_.end()) {
347 return it->second;
348 }
349 if (auto it = quotes_.find(domCcy + forCcy); it != quotes_.end()) {
350 auto f = [](Real x) { return 1.0 / x; };
351 return Handle<Quote>(QuantLib::ext::make_shared<DerivedQuote<decltype(f)>>(it->second, f));
352 }
353 QL_FAIL(
354 "FXTriangulation::getQuote(" << forCcy << domCcy
355 << ") - no such quote available. This is an internal error. Contact dev. Quotes = "
356 << getAllQuotes());
357}
358
359std::string FXTriangulation::getAllQuotes() const {
360 std::string result;
361 if (quotes_.size() > 0) {
362 for (auto const& d : quotes_) {
363 result += d.first + ",";
364 }
365 result.erase(std::next(result.end(), -1));
366 }
367 return result;
368}
369
370} // namespace data
371} // namespace ore
Currency and instrument specific conventions/defaults.
Intelligent FX price repository.
QuantLib::ext::shared_ptr< FxIndex > parseFxIndex(const string &s, const Handle< Quote > &fxSpot, const Handle< YieldTermStructure > &sourceYts, const Handle< YieldTermStructure > &targetYts, const bool useConventions)
Convert std::string to QuantExt::FxIndex.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
Definition: parsers.cpp:290
Map text representations to QuantLib/QuantExt types.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
market data related utilties
RandomVariable min(RandomVariable x, const RandomVariable &y)
bool isFxIndex(const std::string &indexName)
std::tuple< Natural, Calendar, BusinessDayConvention > getFxIndexConventions(const string &index)
Definition: marketdata.cpp:160
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
string conversion utilities