@@ -47,15 +47,11 @@ double TargetRedemptionForward::path(double spot,
4747 double dt,
4848 const RatesCurve& foreign_rates,
4949 const RatesCurve& domestic_rates) const {
50- // Each path should return not just the NPV, but also
51- // - the distribution of payments (right?)
52- // - whether it got knocked out, and when
53-
5450 const double direction_multiplier =
5551 (specs_.direction == FxTradeDirection::kLong ) ? 1.0 : -1.0 ;
5652
57- double cumulative_profit = 0 . ;
58- double npv = 0 . ;
53+ PathState state ;
54+ state. current_fx = spot ;
5955
6056 if (dt > specs_.settlement_date_frequency ) {
6157 // This ensures that dt is (at most) the maximum sensible value. It
@@ -72,56 +68,58 @@ double TargetRedemptionForward::path(double spot,
7268 std::round (specs_.settlement_date_frequency / dt);
7369 dt = specs_.settlement_date_frequency / num_timesteps_in_period;
7470
75- double fx = spot;
76- double t = 0 ;
77- double timesteps_taken = 0 ;
78- bool trigger_reached = false ;
79-
80- double r_d =
81- domestic_rates.forwardRate (t, t + specs_.settlement_date_frequency );
82- double r_f =
83- foreign_rates.forwardRate (t, t + specs_.settlement_date_frequency );
71+ double r_d = domestic_rates.forwardRate (
72+ state.current_time ,
73+ state.current_time + specs_.settlement_date_frequency );
74+ double r_f = foreign_rates.forwardRate (
75+ state.current_time ,
76+ state.current_time + specs_.settlement_date_frequency );
8477
85- while (t < specs_.end_date_years && !trigger_reached) {
78+ while (state. current_time < specs_.end_date_years && !state. trigger_reached ) {
8679 const double z = absl::Gaussian<double >(bitgen_, 0 , 1 );
8780 const double stoch_term = sigma * std::sqrt (dt) * z;
8881 const double drift_term = (r_d - r_f - 0.5 * sigma * sigma) * dt;
8982
90- t += dt;
91- ++timesteps_taken ;
92- fx *= std::exp (drift_term + stoch_term);
83+ state. current_time += dt;
84+ ++state. timesteps_since_last_settlement ;
85+ state. current_fx *= std::exp (drift_term + stoch_term);
9386
94- if (timesteps_taken == num_timesteps_in_period) {
95- double payment_amount =
96- direction_multiplier * specs_. notional * (fx - specs_.strike );
87+ if (state. timesteps_since_last_settlement == num_timesteps_in_period) {
88+ double payment_amount = direction_multiplier * specs_. notional *
89+ (state. current_fx - specs_.strike );
9790
9891 // If we reach the target on this payment date, then the current
9992 // payment is truncated to deliver the exact amount remaining
10093 // to hit the target.
101- if (cumulative_profit + payment_amount > specs_.target ) {
102- trigger_reached = true ;
103- payment_amount = specs_.target - cumulative_profit;
94+ if (state. cumulative_profit + payment_amount > specs_.target ) {
95+ state. trigger_reached = true ;
96+ payment_amount = specs_.target - state. cumulative_profit ;
10497 }
10598
10699 if (payment_amount > 0 ) {
107- cumulative_profit += payment_amount;
100+ state. cumulative_profit += payment_amount;
108101 }
109102
110103 // Discount the payment amount on the domestic curve.
111- const double discounted_pmt = payment_amount * domestic_rates.df (t);
112- npv += discounted_pmt;
104+ const double discounted_pmt =
105+ payment_amount * domestic_rates.df (state.current_time );
106+ state.npv += discounted_pmt;
113107
114108 // Reset to the next period.
115- timesteps_taken = 0 ;
109+ state. timesteps_since_last_settlement = 0 ;
116110
117111 // And look up the forward interest rates for the next simulation
118112 // period (we don't do this at each time step to avoid computing these
119113 // excessively, in case dt is very small).
120- r_d = domestic_rates.forwardRate (t, t + specs_.settlement_date_frequency );
121- r_f = foreign_rates.forwardRate (t, t + specs_.settlement_date_frequency );
114+ r_d = domestic_rates.forwardRate (
115+ state.current_time ,
116+ state.current_time + specs_.settlement_date_frequency );
117+ r_f = foreign_rates.forwardRate (
118+ state.current_time ,
119+ state.current_time + specs_.settlement_date_frequency );
122120 }
123121 }
124- return npv;
122+ return state. npv ;
125123}
126124
127125TarfPricingResult TargetRedemptionForward::price (
@@ -146,4 +144,56 @@ TarfPricingResult TargetRedemptionForward::price(
146144 return result;
147145}
148146
147+ double findZeroNPVStrike (const TarfContractSpecs& specs,
148+ double spot,
149+ double sigma,
150+ const RatesCurve& foreign_rates,
151+ const RatesCurve& domestic_rates,
152+ size_t num_paths) {
153+ // TODO: ALSO then verify that the value of one is positive and the other is
154+ // negative.
155+ double atm_fwd = weightedAvgForward (spot,
156+ specs.end_date_years ,
157+ specs.settlement_date_frequency ,
158+ foreign_rates,
159+ domestic_rates);
160+ double k_low = atm_fwd * 0.5 ;
161+ double k_high = atm_fwd * 1.5 ;
162+
163+ TarfContractSpecs k_mid_specs = specs;
164+ k_mid_specs.strike = 0.5 * (k_low + k_high);
165+
166+ double tolerance_pct =
167+ 0.0001 ; // 0.01% difference for starters. Do not hard-code!
168+
169+ // Relatively coarse timesteps.
170+ double dt = specs.settlement_date_frequency * 0.2 ;
171+
172+ // Initial method: bisection.
173+ while (std::abs (k_high / k_low - 1 ) > tolerance_pct) {
174+ TargetRedemptionForward tarf_mid (k_mid_specs);
175+
176+ double npv_mid =
177+ tarf_mid
178+ .price (spot, sigma, dt, num_paths, foreign_rates, domestic_rates)
179+ .mean_npv ;
180+
181+ if (specs.direction == FxTradeDirection::kLong ) {
182+ if (npv_mid > 0 ) {
183+ k_low = k_mid_specs.strike ;
184+ } else {
185+ k_high = k_mid_specs.strike ;
186+ }
187+ } else { // FxTradeDirection::kShort
188+ if (npv_mid > 0 ) {
189+ k_high = k_mid_specs.strike ;
190+ } else {
191+ k_low = k_mid_specs.strike ;
192+ }
193+ }
194+ k_mid_specs.strike = 0.5 * (k_low + k_high);
195+ }
196+ return k_mid_specs.strike ;
197+ }
198+
149199} // namespace smileexplorer
0 commit comments