Coverage Report

Created: 2026-09-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/pricingengines/forward/discountingfxforwardengine.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2026 Chirag Desai
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <https://www.quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
#include <ql/pricingengines/forward/discountingfxforwardengine.hpp>
21
#include <utility>
22
23
namespace QuantLib {
24
25
    DiscountingFxForwardEngine::DiscountingFxForwardEngine(
26
        Handle<YieldTermStructure> sourceCurrencyDiscountCurve,
27
        Handle<YieldTermStructure> targetCurrencyDiscountCurve,
28
        Handle<Quote> spotFx)
29
0
    : sourceCurrencyDiscountCurve_(std::move(sourceCurrencyDiscountCurve)),
30
0
      targetCurrencyDiscountCurve_(std::move(targetCurrencyDiscountCurve)),
31
0
      spotFx_(std::move(spotFx)) {
32
0
        registerWith(sourceCurrencyDiscountCurve_);
33
0
        registerWith(targetCurrencyDiscountCurve_);
34
0
        registerWith(spotFx_);
35
0
    }
36
37
0
    void DiscountingFxForwardEngine::calculate() const {
38
0
        QL_REQUIRE(!sourceCurrencyDiscountCurve_.empty(),
39
0
                   "source currency discount curve handle is empty");
40
0
        QL_REQUIRE(!targetCurrencyDiscountCurve_.empty(),
41
0
                   "target currency discount curve handle is empty");
42
0
        QL_REQUIRE(!spotFx_.empty(), "spot FX quote handle is empty");
43
44
0
        results_.value = 0.0;
45
0
        results_.errorEstimate = Null<Real>();
46
47
0
        const Date& maturityDate = arguments_.maturityDate;
48
0
        const Date& settlementDate = arguments_.settlementDate;
49
50
        // Validate that curve reference dates are on or before settlement date
51
0
        const Date sourceRefDate = sourceCurrencyDiscountCurve_->referenceDate();
52
0
        const Date targetRefDate = targetCurrencyDiscountCurve_->referenceDate();
53
0
        QL_REQUIRE(sourceRefDate <= settlementDate,
54
0
                   "source currency discount curve reference date (" << sourceRefDate
55
0
                   << ") must be on or before settlement date (" << settlementDate << ")");
56
0
        QL_REQUIRE(targetRefDate <= settlementDate,
57
0
                   "target currency discount curve reference date (" << targetRefDate
58
0
                   << ") must be on or before settlement date (" << settlementDate << ")");
59
60
        // Get the spot FX rate (targetCurrency/sourceCurrency)
61
0
        const Real spotFxRate = spotFx_->value();
62
0
        QL_REQUIRE(spotFxRate > 0.0, "spot FX rate must be positive");
63
64
        // Get discount factors from settlement to maturity
65
0
        const DiscountFactor dfSource =
66
0
            sourceCurrencyDiscountCurve_->discount(maturityDate) /
67
0
            sourceCurrencyDiscountCurve_->discount(settlementDate);
68
0
        const DiscountFactor dfTarget =
69
0
            targetCurrencyDiscountCurve_->discount(maturityDate) /
70
0
            targetCurrencyDiscountCurve_->discount(settlementDate);
71
0
        const DiscountFactor dfSourceSettlement =
72
0
            sourceCurrencyDiscountCurve_->discount(settlementDate);
73
0
        const DiscountFactor dfTargetSettlement =
74
0
            targetCurrencyDiscountCurve_->discount(settlementDate);
75
76
        // Calculate fair forward rate: F = S * dfSource / dfTarget
77
        // This is the forward rate targetCurrency/sourceCurrency
78
0
        results_.fairForwardRate = spotFxRate * dfSource / dfTarget;
79
80
        // Calculate settlement-date present values of each leg
81
        // PV of source currency leg (in source currency)
82
0
        const Real pvSource = arguments_.sourceNominal * dfSource;
83
84
        // PV of target currency leg (in target currency)
85
0
        const Real pvTarget = arguments_.targetNominal * dfTarget;
86
87
        // Convert target currency PV to source currency using spot FX rate
88
0
        const Real pvTargetInSourceCurrency = pvTarget / spotFxRate;
89
90
        // Calculate NPV based on direction of trade
91
        // If paySourceCurrency is true: pay source currency, receive target currency
92
        //   NPV = -PVSource + PVTarget (in source currency terms)
93
        // If paySourceCurrency is false: receive source currency, pay target currency
94
        //   NPV = +PVSource - PVTarget (in source currency terms)
95
0
        const Real npvAtSettlementInSourceCurrency =
96
0
            arguments_.paySourceCurrency ?
97
0
                Real(-pvSource + pvTargetInSourceCurrency) :
98
0
                Real(pvSource - pvTargetInSourceCurrency);
99
100
0
        const Real npvInSourceCurrency =
101
0
            npvAtSettlementInSourceCurrency * dfSourceSettlement;
102
0
        const Real npvInTargetCurrency =
103
0
            npvAtSettlementInSourceCurrency * spotFxRate * dfTargetSettlement;
104
105
        // Store results - NPV is as of the curve reference date
106
0
        results_.value = npvInSourceCurrency;
107
0
        results_.npvSourceCurrency = npvInSourceCurrency;
108
0
        results_.npvTargetCurrency = npvInTargetCurrency;
109
110
        // Store additional results for inspection
111
0
        results_.additionalResults["spotFx"] = spotFxRate;
112
0
        results_.additionalResults["sourceCurrencyDiscountFactor"] = dfSource;
113
0
        results_.additionalResults["targetCurrencyDiscountFactor"] = dfTarget;
114
0
        results_.additionalResults["sourceCurrencySettlementDiscountFactor"] =
115
0
            dfSourceSettlement;
116
0
        results_.additionalResults["targetCurrencySettlementDiscountFactor"] =
117
0
            dfTargetSettlement;
118
0
        results_.additionalResults["sourceCurrencyPV"] = pvSource;
119
0
        results_.additionalResults["targetCurrencyPV"] = pvTarget;
120
0
    }
121
122
}