Skip to content

Commit a92c2d9

Browse files
committed
Add support for proper rates curves
1 parent b83fc55 commit a92c2d9

File tree

3 files changed

+68
-16
lines changed

3 files changed

+68
-16
lines changed

derivatives/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ cc_library(
7474
name = "target_redemption_forward",
7575
hdrs = ["target_redemption_forward.h"],
7676
deps = [
77+
"//rates:zero_curve",
7778
"@abseil-cpp//absl/log",
7879
"@abseil-cpp//absl/random",
7980
],

derivatives/target_redemption_forward.h

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "absl/log/log.h"
77
#include "absl/random/random.h"
8+
#include "rates/rates_curve.h"
89

910
// Pricing model for Target Redemption Forward (TARF)
1011

@@ -36,6 +37,8 @@ class TargetRedemptionForward {
3637
double path(double spot,
3738
double sigma,
3839
double dt,
40+
const RatesCurve& foreign_rates,
41+
const RatesCurve& domestic_rates,
3942
absl::BitGen& bitgen) const {
4043
// Each path should return not just the NPV, but also
4144
// - the distribution of payments (right?)
@@ -44,10 +47,6 @@ class TargetRedemptionForward {
4447
double cumulative_profit = 0.;
4548
double npv = 0.;
4649

47-
// Temporary placeholder: hardcode the rates.
48-
double r_f = 0.04;
49-
double r_d = 0.08;
50-
5150
if (dt > settlement_date_frequency_) {
5251
// This ensures that dt is (at most) the maximum sensible value. It
5352
// shouldn't be larger than the periodic settlements. For example if the
@@ -67,9 +66,11 @@ class TargetRedemptionForward {
6766
double timesteps_taken = 0;
6867
bool trigger_reached = false;
6968
while (t < end_date_years_ && !trigger_reached) {
70-
double z = absl::Gaussian<double>(bitgen, 0, 1);
71-
double stoch_term = sigma * std::sqrt(dt) * z;
72-
double drift_term = (r_d - r_f - 0.5 * sigma * sigma) * dt;
69+
const double z = absl::Gaussian<double>(bitgen, 0, 1);
70+
const double stoch_term = sigma * std::sqrt(dt) * z;
71+
double r_d = domestic_rates.forwardRate(t, t + dt);
72+
double r_f = foreign_rates.forwardRate(t, t + dt);
73+
const double drift_term = (r_d - r_f - 0.5 * sigma * sigma) * dt;
7374

7475
t += dt;
7576
++timesteps_taken;
@@ -91,27 +92,30 @@ class TargetRedemptionForward {
9192
}
9293

9394
// Discount the payment amount on the domestic curve.
94-
double discounted_pmt = payment_amount * std::exp(-r_d * t);
95+
const double discounted_pmt = payment_amount * domestic_rates.df(t);
9596
npv += discounted_pmt;
9697

97-
// LOG(INFO) << " t:" << t << " fx:" << fx
98-
// << " pmt amt:" << payment_amount
99-
// << " discounted:" << discounted_pmt;
100-
10198
// Reset to the next period.
10299
timesteps_taken = 0;
103100
}
104101
}
105102
return npv;
106103
}
107104

108-
double price(double spot, double sigma, double dt, size_t num_paths) const {
105+
double price(double spot,
106+
double sigma,
107+
double dt,
108+
size_t num_paths,
109+
// Convention: fx rate is quoted as FOR-DOM:
110+
const RatesCurve& foreign_rates,
111+
const RatesCurve& domestic_rates) const {
109112
absl::BitGen bitgen;
110113
if (num_paths == 0) return 0.;
111114

112115
double mean_npv = 0.;
113116
for (size_t i = 1; i <= num_paths; ++i) {
114-
double path_npv = path(spot, sigma, dt, bitgen);
117+
double path_npv =
118+
path(spot, sigma, dt, foreign_rates, domestic_rates, bitgen);
115119
// Compute the online mean at each step for numerical stability.
116120
mean_npv += (path_npv - mean_npv) / static_cast<double>(i);
117121
}

derivatives/target_redemption_forward_test.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <gtest/gtest.h>
44

5+
#include "rates/zero_curve.h"
6+
57
namespace smileexplorer {
68
namespace {
79

@@ -23,13 +25,19 @@ TEST(TargetRedemptionForwardTest, AtmForward) {
2325
strip of forwards. For reference, the accumulated profit of the profitable
2426
forwards would be 48mm ISK.
2527
*/
28+
ZeroSpotCurve domestic_curve(
29+
{1, 3}, {0.08, 0.08}, CompoundingPeriod::kContinuous);
30+
ZeroSpotCurve foreign_curve(
31+
{1, 3}, {0.04, 0.04}, CompoundingPeriod::kContinuous);
32+
2633
const double expected_npv = 0.;
2734
const double error_threshold = 20000;
2835

2936
TargetRedemptionForward tarf(1e6, 100e6, 135.657, 4.0, 0.25);
3037

3138
for (int i = 0; i < 5; ++i) {
32-
double npv = tarf.price(125., 0.0004, 0.1, 10000);
39+
double npv =
40+
tarf.price(125., 0.0004, 0.1, 10000, foreign_curve, domestic_curve);
3341
EXPECT_LT(std::abs(npv - expected_npv), error_threshold);
3442
}
3543
}
@@ -44,12 +52,51 @@ TEST(TargetRedemptionForwardTest, OtmForward) {
4452
in an NPV of almost exactly 50mm ISK.
4553
*/
4654

55+
ZeroSpotCurve domestic_curve(
56+
{1, 3}, {0.08, 0.08}, CompoundingPeriod::kContinuous);
57+
ZeroSpotCurve foreign_curve(
58+
{1, 3}, {0.04, 0.04}, CompoundingPeriod::kContinuous);
59+
4760
const double expected_npv = 50e6;
4861
const double error_threshold = 20000;
4962

5063
TargetRedemptionForward tarf(1e6, 100e6, 131.9686, 4.0, 0.25);
5164
for (int i = 0; i < 5; ++i) {
52-
double npv = tarf.price(125., 0.0004, 0.1, 10000);
65+
double npv =
66+
tarf.price(125., 0.0004, 0.1, 10000, foreign_curve, domestic_curve);
67+
68+
EXPECT_LT(std::abs(npv - expected_npv), error_threshold);
69+
}
70+
}
71+
72+
TEST(TargetRedemptionForwardTest, KnockoutAlmostDeterministic) {
73+
/*
74+
Similar to the AtmForward case above, but with a 6mm ISK cumulative profit
75+
target. At a very low vol, this is almost certain to happen at year 2.75 (with
76+
positive payments at t=[2.25, 2.5, 2.75]).
77+
78+
The expected NPV is very negative here, because we are using the ATM fwd as
79+
the strike, yet we have a fairly low profit target and a large interest-rate
80+
differential. Therefore, there are large accumulated losses in the first 2
81+
years (around -39mm ISK, not discounted) and only +6mm ISK worth of profits
82+
due to the fixed target.
83+
*/
84+
85+
ZeroSpotCurve domestic_curve(
86+
{1, 3}, {0.08, 0.08}, CompoundingPeriod::kContinuous);
87+
ZeroSpotCurve foreign_curve(
88+
{1, 3}, {0.04, 0.04}, CompoundingPeriod::kContinuous);
89+
90+
// This is the discounted amount of the approx. -33mm in total losses (-39mm +
91+
// 6mm):
92+
const double expected_npv = -31.75e6;
93+
const double error_threshold = 20000;
94+
95+
TargetRedemptionForward tarf(1e6, 6e6, 135.657, 4.0, 0.25);
96+
97+
for (int i = 0; i < 5; ++i) {
98+
double npv =
99+
tarf.price(125., 0.0004, 0.1, 10000, foreign_curve, domestic_curve);
53100
EXPECT_LT(std::abs(npv - expected_npv), error_threshold);
54101
}
55102
}

0 commit comments

Comments
 (0)