The forward isn't quoted — it has to be rebuilt.
Index options are quoted against a forward
\(F(t)=S_0e^{(r(t)-q(t))t}\) that is not directly
observable. The rate \(r(t)\) is bootstrapped from
EURIBOR 6M zero-coupon rates with a PCHIP
interpolation. The cost of carry \(q(t)\), which
combines dividends and repo, is neither flat nor
quoted, and for the CAC40 it is strongly seasonal,
concentrated between April and June.
The effect of a wrong forward can be quantified.
Let \(\hat q\) be the carry used instead of the true
\(q\), and \(\hat F\) the resulting forward. Holding
the volatility dynamics fixed:
If \(\hat q=0\lt q\), the forward is too high: every
call is overpriced and every put underpriced. At the
longest maturity, \(T=4.84\), omitting a constant
\(q=3\%\) overstates the forward by
\(e^{0.1452}-1\approx15.6\%\). The bias is not only
vertical: in log-moneyness, the whole smile is
shifted horizontally.
Here the shift is about 0.145, a third of the total
standard deviation \(\sigma\sqrt T\approx0.44\) for
an illustrative 20% volatility. No Heston parameter
set can reproduce a horizontal translation, so the
optimizer tries to mimic it with extreme values of
\(\rho\) and \(\xi\).
Empirical diagnosis. With
\(q\equiv0\), the fit is excellent below one year,
but at \(T=4.84\) calls are overpriced and puts
underpriced, with errors up to €650 on individual
quotes and a global price RMSE of €135. A single
constant \(q=3\%\) brings the RMSE down to €32, a
76% reduction. The residual error comes from a flat
\(q\) that cannot follow the seasonal dividends.
For each expiry, put-call parity implies that the
call and put price curves cross exactly at the
forward. Call and put quotes are interpolated in
strike with a shape-preserving PCHIP interpolator
(cubic splines would create spurious oscillations),
and the crossing is found by bisection on
\(K\mapsto C-P=D(T)(F(T)-K)\), which is strictly
decreasing:
Parity is even linear in \(K\):
\(C_i-P_i=\alpha+\beta K_i\). A least-squares fit on
near-the-money strikes averages out quote noise and
also returns an implied discount factor, a
consistency check of the rate curve:
\(\hat F(T)=-\hat\alpha/\hat\beta\) and
\(\hat D(T)=-\hat\beta\approx e^{-r(T)T}\).
Assuming \(q(t)\) piecewise constant between listed
expiries \(T_1\lt\cdots\lt T_n\), each segment is
solved recursively:
The curve is extrapolated flat before \(T_1\) and
after \(T_n\), and the forward at any maturity is
\(F(T)=S_0\exp\big(r(T)T-\int_0^T q(t)\,dt\big)\).
The bootstrap is a discrete derivative, so it
amplifies noise: an error \(\delta_k\) on
\(\ln F(T_k)\) becomes
With two expiries 4 days apart, a 1 basis-point
error on \(\ln F\) turns into about 0.9% on
\(q_k\). The bootstrapped segments at short
maturities must therefore be checked, or the
cumulative carry smoothed, before use.
The machinery is split into two single-responsibility
classes, usable by any pricing engine:
ForwardCurve
Takes listed strikes and call/put prices per
expiry, solves the parity bisection and
exposes calc_forward(T) for any
\(T\).
DividendCurve
Takes the implied forwards and the rate
curve, runs the bootstrap and exposes a
callable q(T), a drop-in
replacement for the scalar \(q\) of
HestonModel. It needs no option
data, so it can also be built from futures
prices.
The pipeline: (1) solve parity for each expiry to
get \(F(T_k)\); (2) bootstrap \(q_k\);
(3) evaluate \(F(T)\), \(q(T)\) and \(D(T)\) at each
quote's maturity; (4) invert prices into implied
volatilities with these inputs; (5) minimize the
loss.