Skip to content

Commit 3d6799b

Browse files
committed
Add weightedAvgForward function for computing vanilla (no knockout) atmfwd strikes
1 parent 5eb4149 commit 3d6799b

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

derivatives/target_redemption_forward.h

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,47 @@ class TargetRedemptionForward {
3434
settlement_date_frequency_(settlement_date_frequency),
3535
direction_(direction) {}
3636

37+
/* Returns the weighted average of the forward FX rate, weighted by the
38+
discount factors.
39+
40+
As a simple example, suppose spot is at 100 and there are 3 periods of
41+
interest, with the following forward FX rates and the following discount
42+
factors from the `domestic_rates` curve:
43+
44+
t (yrs) fwd df
45+
------- --- ---
46+
1 102 0.95
47+
2 104 0.90
48+
3 106 0.85
49+
50+
Then this function would return ~103.9259
51+
or in spreadsheet pseudocode:
52+
= sumproduct(fwd, df)/sum(df)
53+
*/
54+
double weightedAvgForward(double spot,
55+
const RatesCurve& foreign_rates,
56+
const RatesCurve& domestic_rates) const {
57+
double sumproduct = 0.;
58+
double fx = spot;
59+
double df_sum = 0.;
60+
61+
int num_payments = std::round(end_date_years_ / settlement_date_frequency_);
62+
for (int i = 1; i <= num_payments; ++i) {
63+
double t_init = (i - 1) * settlement_date_frequency_;
64+
double t_final = i * settlement_date_frequency_;
65+
double rd = domestic_rates.forwardRate(t_init, t_final);
66+
double rf = foreign_rates.forwardRate(t_init, t_final);
67+
fx *= std::exp((rd - rf) * settlement_date_frequency_);
68+
sumproduct += fx * domestic_rates.df(t_final);
69+
df_sum += domestic_rates.df(t_final);
70+
}
71+
72+
// Sanity check in case of degenerate case.
73+
if (df_sum <= 0.) return 0.;
74+
75+
return sumproduct / df_sum;
76+
}
77+
3778
// Initial implementation: given a flat volatility and a specified forward,
3879
// price the scenarios using the provided discount curve. (It is up to the
3980
// client to ensure that the current discount curve is provided, otherwise a
@@ -162,15 +203,23 @@ inline double findZeroNPVStrike(double notional,
162203
// TODO: compute the forward for a more intelligent starting guess.
163204
// AND ALSO then verify that the value of one is positive and the other is
164205
// negative.
165-
double k_low = spot * 0.5;
166-
double k_high = spot * 2;
206+
TargetRedemptionForward tarf_forward(notional,
207+
target,
208+
spot,
209+
end_date_years,
210+
settlement_date_frequency,
211+
direction);
212+
double atm_fwd =
213+
tarf_forward.weightedAvgForward(spot, foreign_rates, domestic_rates);
214+
double k_low = atm_fwd * 0.5;
215+
double k_high = atm_fwd * 1.1;
167216
double k_mid = 0.5 * (k_low + k_high);
168217

169218
double tolerance_pct =
170219
0.0001; // 0.01% difference for starters. Do not hard-code!
171220

172-
// TODO is there ever a need to have this be smaller than the period?
173-
double dt = settlement_date_frequency * 0.5;
221+
// Relatively coarse timesteps.
222+
double dt = settlement_date_frequency * 0.2;
174223

175224
// Initial method: bisection.
176225
while (std::abs(k_high / k_low - 1) > tolerance_pct) {
@@ -182,7 +231,7 @@ inline double findZeroNPVStrike(double notional,
182231
direction);
183232

184233
double npv_mid =
185-
tarf_mid.price(spot, sigma, dt, 4000, foreign_rates, domestic_rates);
234+
tarf_mid.price(spot, sigma, dt, 5000, foreign_rates, domestic_rates);
186235

187236
if (npv_mid > 0) {
188237
k_low = k_mid;

derivatives/target_redemption_forward_test.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class TargetRedemptionForwardTest : public ::testing::Test {
2424
std::unique_ptr<ZeroSpotCurve> domestic_curve_;
2525
};
2626

27-
TEST_F(TargetRedemptionForwardTest, AtmForward) {
27+
TEST_F(TargetRedemptionForwardTest, DeterministicForwardWithoutTarget) {
2828
/*
2929
Verified in a spreadsheet:
3030
- expiry: 4 years
@@ -33,10 +33,25 @@ TEST_F(TargetRedemptionForwardTest, AtmForward) {
3333
- spot fx rate: 125.0
3434
- USD (foreign) rate: 4%
3535
- ISK (domestic) rate: 8%
36-
- notional: $1,000,000
3736
38-
At this interest rate differential, the discount-weighted average of the
37+
Rates are flat and continuously compounded.
38+
39+
At this interest rate differential, the discount-weighted average of the
3940
forwards is 135.657.
41+
*/
42+
43+
TargetRedemptionForward tarf(
44+
100, 100, 125, 4.0, 0.25, FxTradeDirection::kLong);
45+
46+
EXPECT_NEAR(135.6570005,
47+
tarf.weightedAvgForward(125, *foreign_curve_, *domestic_curve_),
48+
1e-6);
49+
}
50+
51+
TEST_F(TargetRedemptionForwardTest, AtmForwardHasZeroNPV) {
52+
/*
53+
This test uses the parameters described in DeterministicForwardWithoutTarget
54+
above.
4055
4156
With a high-enough target (here, 100mm ISK) and a very low vol, this is just a
4257
strip of forwards. For reference, the accumulated profit of the profitable
@@ -46,8 +61,14 @@ TEST_F(TargetRedemptionForwardTest, AtmForward) {
4661
const double expected_npv = 0.;
4762
const double error_threshold = 20000;
4863

64+
// This step is just to discover the appropriate strike.
65+
TargetRedemptionForward placeholder_tarf(
66+
100, 100, 125, 4.0, 0.25, FxTradeDirection::kLong);
67+
double atm_fwd_strike = placeholder_tarf.weightedAvgForward(
68+
125, *foreign_curve_, *domestic_curve_);
69+
4970
TargetRedemptionForward tarf(
50-
1e6, 100e6, 135.657, 4.0, 0.25, FxTradeDirection::kLong);
71+
1e6, 100e6, atm_fwd_strike, 4.0, 0.25, FxTradeDirection::kLong);
5172

5273
for (int i = 0; i < 5; ++i) {
5374
double npv =
@@ -63,7 +84,8 @@ TEST_F(TargetRedemptionForwardTest, OtmForward) {
6384
of the NPV computations is correct.
6485
6586
At a strike of 131.9686 and the other params kept the same, this should result
66-
in an NPV of almost exactly 50mm ISK.
87+
in an NPV of almost exactly 50mm ISK. This was verified / computed in a
88+
spreadsheet.
6789
*/
6890

6991
const double expected_npv = 50e6;

0 commit comments

Comments
 (0)