Monte Carlo pricing of the payoffs that make up most of a retail structured products book: Phoenix and Athena autocalls, reverse convertibles, shark notes and credit-linked notes. Everything is priced from simulated paths, with the analytics a salesperson needs in his head on top: when the note is likely to be called, how much coupon it should actually pay, and how the capital behaves in the tail.
I wrote this after two internships on structured products desks (Natixis CIB, Kepler Cheuvreux). On the desk I priced these payoffs on the bank's own tools, which give you a number and not a reason; this is my own implementation of the same structures, built to see what sits behind the number.
| Product | Payoff |
|---|---|
Autocallable |
Phoenix: conditional coupon with memory, early redemption at par, capital barrier at maturity. Set the coupon to zero and add a redemption_premium to get an Athena. |
ReverseConvertible |
Unconditional coupon, capital indexed to the underlying below the barrier. Long a bond, short a down-and-in put. |
SharkNote |
Capital guaranteed, participation in the upside that knocks out if the barrier is touched, fixed rebate if it is. |
CreditLinkedNote |
Coupon note on a reference entity, default times from a constant-intensity model, recovery paid on the default date. |
python examples/compare_products.py, spot 100, vol 22%, rate 3%, dividend
yield 2.5%, 200k antithetic paths:
Product Price SE Coupon Life
----------------------------------------------------------
Phoenix autocall 5Y 99.239 0.035 7.60 1.38
Athena autocall 5Y 94.335 0.049 0.00 2.63
Reverse convertible 1Y 100.982 0.022 9.00 1.00
Shark note 3Y 93.421 0.006 2.22 3.00
Credit-linked note 4Y 93.469 0.040 11.23 4.00
Price and coupon are in points of nominal, life is the expected time to redemption in years.
python examples/price_phoenix.py breaks one note down in full. On the 5Y
Phoenix above, struck at 100 with a 70% coupon barrier, a 100% autocall barrier
and a 60% capital barrier:
- 48.2% of paths are called on the very first quarterly observation, and 84.2% are called at some point before maturity
- expected life is 1.38 years against a 5 year legal maturity
- average coupon collected is 7.60 points, but the median is 3.50: the mean is pulled up by the 16% of paths that survive and keep paying
- 8.6% of paths end with a capital loss, and when that happens the average redemption is 44.9 points
That last pair of numbers is the whole point of the product. The note looks like a 7% coupon and behaves like one most of the time, but the downside is concentrated and severe.
The value profile is concave and flattens out just above par: once the spot is comfortably above the autocall barrier the note is going to be called at 100, so there is nothing left to gain. That is the same thing as saying the investor is long delta, short gamma and short vega, which is what the engine reports:
delta 0.2000 per 1% move in the spot
gamma -0.0266
vega -0.4501 per volatility point
rho -0.0049 per basis point
Barriers are pinned to the strike level, not to the simulated spot. This
sounds obvious but it is easy to get wrong: if you compute performance as
path / path[0], the payoff becomes homogeneous of degree zero in the spot,
every barrier follows the underlying under a bump, and the note shows a delta
of exactly zero. Products therefore carry an initial_level, and price()
refuses to run until it is set. strike_at_inception(product, market) sets it
to today's spot; to price a live note, set it to the level actually fixed at
inception and move market.spot to where the underlying trades now.
Maturity has three outcomes, not two. Above the final trigger the note
redeems like an early call, accrued premium included. Between the trigger and
the capital barrier the investor gets their money back and nothing more. Below
it, capital is indexed to the underlying. Collapsing the first two cases, and
paying an Athena's accrued premium all the way down to the protection barrier,
is worth about five points on a five year note: 15% of paths finish between the
two barriers. final_trigger defaults to the autocall barrier, which is what a
standard term sheet says, and can be lowered explicitly for the structures that
do set a separate final coupon barrier.
Coupon and redemption legs are kept separate in the Cashflows record, so
the income and the capital risk can be measured independently without trying to
reverse-engineer one out of the other.
Antithetic sampling is on by default. Since that makes paths come in dependent pairs, the standard error is computed on the pair averages rather than on the individual paths, which would understate it.
Greeks reuse the random numbers across the bumped and unbumped runs. Without that, the Monte Carlo noise swamps the difference and gamma is unusable.
The tests check the engine against cases where the answer is known:
E[S_T] = S_0 e^{(r-q)T}, so the simulated forward is a martingale- a reverse convertible with its barrier at 100% is exactly a zero-coupon bond plus its coupon minus a vanilla put, which Black-Scholes prices in closed form; the Monte Carlo agrees within four standard errors
- a credit-linked note with zero recovery is the survival-weighted sum of its cashflows, again matched within four standard errors
- an autocall with its barrier at zero is called on the first date with
probability one and must price to
(100 + coupon) * exp(-r * t_1) - no Athena path redeems strictly between par and par plus one premium, which is the test that pins the maturity split
- a Phoenix is unchanged by
final_trigger, since it has no premium to pay - memory pays two coupons at once on a hand-built path that misses the barrier and then meets it
- capital on a shark note is never below par, on any path
- antithetic sampling reduces the standard error
- raising the coupon barrier lowers the price
- delta is positive, gamma and vega negative
- pricing an unstruck product raises rather than silently returning a zero delta
python -m pytest tests/ -q
16 passed
The model is flat Black-Scholes: one volatility, one rate, a continuous
dividend yield. That is enough to get the structure of the payoff right and to
reason about the risk, but it is not what a desk quotes on. In particular a
real autocall is very sensitive to the volatility skew, because the capital
barrier sits far out of the money, and flat vol will systematically
underestimate how expensive that downside is. Discrete dividends matter too on
single stocks. A local volatility or Heston path generator would slot in behind
gbm_paths without touching the payoffs.
Barrier monitoring is discrete, on the simulation grid. For the shark note,
whose barrier is continuous in the term sheet, pass a daily grid
(n_steps=252*T) or the note will price too high.
pip install -r requirements.txt
python examples/price_phoenix.py
python examples/compare_products.py
python -m pytest tests/ -q
MIT.

