Simulating Path-Dependent Snowball Coupons with QuantLib
Summary
The document asks how to represent a snowball coupon that accumulates a prior coupon plus a floor-based payoff in QuantLib. The response explains the state dependence implied by that formula: each period's coupon depends on the previous period's coupon, so the example models the payoff through an explicit pathwise recurrence rather than a standalone payoff class. It sketches a Monte Carlo setup using a Hull–White short-rate process, simulated paths, quarterly dates, and discounting to estimate cash flows.
The example initializes the accumulated coupon from a previously observed rate, then adds a multiple of the positive difference between a floor strike and the simulated rate at each step. It calculates discounted coupon cash flows across simulated paths and averages them. This offers a practical illustration of implementing a path-dependent payoff in a simulation loop. The post is an exploratory code example, however, and does not compare approaches, validate the result, or discuss calibration, convergence, or model risk; its author also notes that a more efficient implementation may exist.
Key ideas
- A snowball coupon is path dependent because each payment can incorporate the previous coupon.
- The example updates coupon values recursively for each simulated interest-rate path.
- It uses a Hull–White process to generate rates and discounts periodic cash flows.
- Averaging discounted simulated cash flows provides an estimate of the payoff value.
Tags
Full text
# Structured Payoff Scripting in QuantLib
# Structured Payoff Scripting in QuantLib
I'm trying to price a snowball payoff in quantlib and would like to create a payoff like:
$$Coupon = PreviousCoupon + FloorPayoff$$
Would the payoff class be able to reference the previous coupon? How could I do that? Could anyone point me to an example of implementing the Payoff class to create a payoff script?
I had a look at these slides: https://www.quantlib.org/slides/qlum17/schlenkrich.pdf
## Answer by David Duarte (score 2)
https://quant.stackexchange.com/a/41836
Here is an attempt to code it without using the payoff class but I'm sure there is a more efficient way:
```
import QuantLib as ql
import numpy as np
# global data
todaysDate = ql.Date(31, ql.August, 2018);
ql.Settings.instance().evaluationDate = todaysDate
settlementDate = ql.Date(2, ql.September, 2018)
# Setup the yield termstructure
rate = ql.SimpleQuote(0.03)
rate_handle = ql.QuoteHandle(rate)
dc = ql.Actual365Fixed()
yieldCurve = ql.FlatForward(todaysDate, rate_handle, dc)
yieldCurve.enableExtrapolation()
yieldCurveHandle = ql.YieldTermStructureHandle(yieldCurve)
# HW1F Fixed Params
sigma = 0.003
a = 0.03
timestep = 17
length = 4.25 # in years
forward_rate = -0.0026
day_count = ql.Thirty360()
hw_process = ql.HullWhiteProcess(yieldCurveHandle, a, sigma)
rng = ql.GaussianRandomSequenceGenerator(ql.UniformRandomSequenceGenerator(timestep, ql.UniformRandomGenerator()))
seq = ql.GaussianPathGenerator(hw_process, length, timestep, rng, False)
# Generate Paths
def generate_paths(num_paths, timestep):
arr = np.zeros((num_paths, timestep+1))
for i in range(num_paths):
sample_path = seq.next()
path = sample_path.value()
time = [path.time(j) for j in range(len(path))]
value = [path[j] for j in range(len(path))]
arr[i, :] = np.array(value)
return np.array(time), arr
num_paths = 2000
time, paths = generate_paths(num_paths, timestep)
# Derivative Details
effective_date = ql.Date(13, 6, 2018)
termination_date = ql.Date(13, 12, 2022)
notionals = np.array([100])
tenor = ql.Period(ql.Quarterly)
calendar = ql.TARGET()
business_convention = ql.ModifiedFollowing
termination_business_convention = ql.Following
date_generation = ql.DateGeneration.Forward
end_of_month = False
schedule = ql.Schedule(effective_date,
termination_date,
tenor,
calendar,
business_convention,
termination_business_convention,
date_generation,
end_of_month)
days = schedule.__len__()
dcf = np.array([ql.Actual360().yearFraction(schedule[x], schedule[x+1]) for x in range(days-1) ])
last_rate = 124.574 / 100
multi = 2
floor_strike = 0.02
snowball_rate = np.empty([num_paths, 18])
for i in range(18):
if i == 0:
snowball_rate[:, 0] = last_rate
else:
snowball_rate[:, i] = snowball_rate[:, i - 1] + multi * np.maximum((floor_strike - paths[:, i]), 0)
discountDates = [ (d - settlementDate)/365 for d in schedule]
discountRates = np.array([yieldCurve.discount(d) for d in discountDates[1:]])
cf = notionals * dcf * snowball_rate * discountRates
cf.mean(0).sum()
```Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.