Skip to content
All library documents

Simulating Path-Dependent Snowball Coupons with QuantLib

Article Quant Q&A · Author: David Duarte

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.