Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
discountingfxforwardengine.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
19#include <ql/event.hpp>
20
23
24namespace QuantExt {
25
27 const Currency& ccy1, const Handle<YieldTermStructure>& currency1Discountcurve, const Currency& ccy2,
28 const Handle<YieldTermStructure>& currency2Discountcurve, const Handle<Quote>& spotFX,
29 boost::optional<bool> includeSettlementDateFlows, const Date& settlementDate, const Date& npvDate)
30 : ccy1_(ccy1), currency1Discountcurve_(currency1Discountcurve), ccy2_(ccy2),
31 currency2Discountcurve_(currency2Discountcurve), spotFX_(spotFX),
32 includeSettlementDateFlows_(includeSettlementDateFlows), settlementDate_(settlementDate), npvDate_(npvDate) {
33 registerWith(currency1Discountcurve_);
34 registerWith(currency2Discountcurve_);
35 registerWith(spotFX_);
36}
37
39 Date npvDate = npvDate_;
40 if (npvDate == Null<Date>()) {
41 npvDate = currency1Discountcurve_->referenceDate();
42 }
43 Date settlementDate = settlementDate_;
44 if (settlementDate == Null<Date>()) {
45 settlementDate = npvDate;
46 }
47
48 Real tmpNominal1, tmpNominal2;
49 bool tmpPayCurrency1;
50 if (ccy1_ == arguments_.currency1) {
51 QL_REQUIRE(ccy2_ == arguments_.currency2, "mismatched currency pairs ("
52 << ccy1_ << "," << ccy2_ << ") in the engine and ("
53 << arguments_.currency1 << "," << arguments_.currency2
54 << ") in the instrument");
55 tmpNominal1 = arguments_.nominal1;
56 tmpNominal2 = arguments_.nominal2;
57 tmpPayCurrency1 = arguments_.payCurrency1;
58 } else {
59 QL_REQUIRE(ccy1_ == arguments_.currency2 && ccy2_ == arguments_.currency1,
60 "mismatched currency pairs (" << ccy1_ << "," << ccy2_ << ") in the engine and ("
61 << arguments_.currency1 << "," << arguments_.currency2
62 << ") in the instrument");
63 tmpNominal1 = arguments_.nominal2;
64 tmpNominal2 = arguments_.nominal1;
65 tmpPayCurrency1 = !arguments_.payCurrency1;
66 }
67
68 QL_REQUIRE(!currency1Discountcurve_.empty() && !currency2Discountcurve_.empty(),
69 "Discounting term structure handle is empty.");
70
71 QL_REQUIRE(currency1Discountcurve_->referenceDate() == currency2Discountcurve_->referenceDate(),
72 "Term structures should have the same reference date.");
73
74 QL_REQUIRE(arguments_.payDate >= currency1Discountcurve_->referenceDate(),
75 "FX forward maturity should exceed or equal the "
76 "discount curve reference date.");
77
78 results_.value = 0.0;
79 results_.fairForwardRate = ExchangeRate(ccy2_, ccy1_, tmpNominal1 / tmpNominal2); // strike rate
80 results_.additionalResults["fairForwardRate"] = tmpNominal1 / tmpNominal2;
81 results_.additionalResults["currency[1]"] = ccy1_.code();
82 results_.additionalResults["currency[2]"] = ccy2_.code();
83
84 // The instrument flag overrides what is passed to the engine c'tor
85 bool includeSettlementDateFlows = arguments_.includeSettlementDateFlows;
86
87 if (!detail::simple_event(arguments_.payDate).hasOccurred(settlementDate, includeSettlementDateFlows)) {
88 Real disc1near = currency1Discountcurve_->discount(npvDate);
89 Real disc1far = currency1Discountcurve_->discount(arguments_.payDate);
90 Real disc2near = currency2Discountcurve_->discount(npvDate);
91 Real disc2far = currency2Discountcurve_->discount(arguments_.payDate);
92 Real fxfwd = disc1near / disc1far * disc2far / disc2near * spotFX_->value();
93
94 // settle ccy is ccy1 if no pay ccy provided
95 Currency settleCcy = arguments_.payCcy.empty() ? ccy1_ : arguments_.payCcy;
96 bool settleCcy1 = ccy1_ == settleCcy;
97
98 Real discNear = settleCcy1 ? disc1near : disc2near;
99 Real discFar = settleCcy1 ? disc1far : disc2far;
100 Real fx1 = settleCcy1 ? 1.0 : fxfwd;
101 Real fx2 = settleCcy1 ? 1 / fxfwd : 1.0;
102
103 QL_REQUIRE(arguments_.isPhysicallySettled || arguments_.payDate <= arguments_.fixingDate ||
104 arguments_.fxIndex != nullptr,
105 "If pay date (" << arguments_.payDate << ") is strictly after fixing date (" << arguments_.fixingDate
106 << "), an FX Index must be given for a cash-settled FX Forward.");
107 if (!arguments_.isPhysicallySettled && arguments_.payDate >= arguments_.fixingDate &&
108 arguments_.fxIndex != nullptr) {
109 fx1 = settleCcy1 ? 1.0 : arguments_.fxIndex->fixing(arguments_.fixingDate);
110 fx2 = settleCcy1 ? arguments_.fxIndex->fixing(arguments_.fixingDate) : 1.0;
111 fxfwd = arguments_.fxIndex->fixing(arguments_.fixingDate);
112 }
113
114 // populate cashflow results
115 std::vector<CashFlowResults> cashFlowResults;
116 CashFlowResults cf1, cf2;
117 cf1.payDate = arguments_.payDate;
118 cf1.type = "Notional";
119 cf2.payDate = arguments_.payDate;
120 cf2.type = "Notional";
121 if (!arguments_.isPhysicallySettled) {
122 if (arguments_.payDate >= arguments_.fixingDate) {
123 cf1.fixingDate = arguments_.fixingDate;
124 cf2.fixingDate = arguments_.fixingDate;
125 }
126 cf1.fixingValue = 1.0 / fx1;
127 cf2.fixingValue = 1.0 / fx2;
128 cf1.amount = (tmpPayCurrency1 ? -1.0 : 1.0) * tmpNominal1 / fx1;
129 cf2.amount = (tmpPayCurrency1 ? -1.0 : 1.0) * (-tmpNominal2 / fx2);
130 cf1.currency = settleCcy.code();
131 cf2.currency = settleCcy.code();
132 } else {
133 cf1.amount = (tmpPayCurrency1 ? -1.0 : 1.0) * tmpNominal1;
134 cf2.amount = (tmpPayCurrency1 ? -1.0 : 1.0) * (-tmpNominal2);
135 cf1.currency = ccy1_.code();
136 cf2.currency = ccy2_.code();
137 }
138 if (ccy1_ == arguments_.currency1) {
139 cf1.legNumber = 0;
140 cf2.legNumber = 1;
141 cashFlowResults.push_back(cf1);
142 cashFlowResults.push_back(cf2);
143 } else {
144 cf1.legNumber = 1;
145 cf2.legNumber = 0;
146 cashFlowResults.push_back(cf2);
147 cashFlowResults.push_back(cf1);
148 }
149 results_.additionalResults["cashFlowResults"] = cashFlowResults;
150
151 results_.value = (tmpPayCurrency1 ? -1.0 : 1.0) * discFar / discNear * (tmpNominal1 / fx1 - tmpNominal2 / fx2);
152
153 results_.npv = Money(settleCcy, results_.value);
154
155 results_.fairForwardRate = ExchangeRate(ccy2_, ccy1_, fxfwd);
156 results_.additionalResults["fairForwardRate"] = fxfwd;
157 results_.additionalResults["fxSpot"] = spotFX_->value();
158 results_.additionalResults["discountFactor[1]"] = disc1far;
159 results_.additionalResults["discountFactor[2]"] = disc2far;
160 results_.additionalResults["legNPV[1]"] = (tmpPayCurrency1 ? -1.0 : 1.0) * discFar / discNear * tmpNominal1 / fx1;
161 results_.additionalResults["legNPV[2]"] = (tmpPayCurrency1 ? -1.0 : 1.0) * discFar / discNear * (-tmpNominal2 / fx2);
162
163 // set notional
164 if (arguments_.isPhysicallySettled) {
165 // Align notional with ISDA AANA/GRID guidance as of November 2020 for deliverable forwards
166 if (tmpNominal1 > tmpNominal2 * fxfwd) {
167 results_.additionalResults["currentNotional"] = tmpNominal1;
168 results_.additionalResults["notionalCurrency"] = ccy1_.code();
169 } else {
170 results_.additionalResults["currentNotional"] = tmpNominal2;
171 results_.additionalResults["notionalCurrency"] = ccy2_.code();
172 }
173 } else {
174 // for cash settled forwards we take the notional from the settlement ccy leg
175 results_.additionalResults["currentNotional"] =
176 arguments_.currency1 == arguments_.payCcy ? arguments_.nominal1 : arguments_.nominal2;
177 results_.additionalResults["notionalCurrency"] = arguments_.payCcy.code();
178 }
179 }
180
181} // calculate
182
183} // namespace QuantExt
class holding cashflow-related results
const Instrument::results * results_
Definition: cdsoption.cpp:81
DiscountingFxForwardEngine(const Currency &ccy1, const Handle< YieldTermStructure > &currency1Discountcurve, const Currency &ccy2, const Handle< YieldTermStructure > &currency2Discountcurve, const Handle< Quote > &spotFX, boost::optional< bool > includeSettlementDateFlows=boost::none, const Date &settlementDate=Date(), const Date &npvDate=Date())
Handle< YieldTermStructure > currency2Discountcurve_
Handle< YieldTermStructure > currency1Discountcurve_
Engine to value an FX Forward off two yield curves.
Swap::arguments * arguments_