Numerical Greeks
Hello, dear reader.
This post shows a short notebook on numerical Greeks. If you bought the QuantLib Python Cookbook, or if you watched my videos on YouTube, you might have already seen an earlier version of the same notebook. However, a recent question on the Quantitative Finance Stack Exchange made me realize that I should give it some exposure here, too.
The notebook shows how to calculate numerical Greeks when a pricing engine doesn’t provide them. As to why the engine might not provide it—well, it turns out that I already wrote about it in 2017 and I didn’t remember it. Here is a link to that post. Enjoy!
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
Numerical Greeks
In this notebook, I’ll build on the facilities provided by the
Instrument
class (that is, its ability to detect changes in its inputs
and recalculate accordingly) to show how to calculate numerical Greeks
when a given engine doesn’t provide them.
Setup
As usual, we import the QuantLib module and set the evaluation date:
import QuantLib as ql
today = ql.Date(8, ql.October, 2014)
ql.Settings.instance().evaluationDate = today
A somewhat exotic option
As an example, we’ll use a knock-in barrier option:
strike = 100.0
barrier = 120.0
rebate = 30.0
option = ql.BarrierOption(
ql.Barrier.UpIn,
barrier,
rebate,
ql.PlainVanillaPayoff(ql.Option.Call, strike),
ql.EuropeanExercise(ql.Date(8, ql.January, 2015)),
)
For the purpose of this example, the market data are the underlying value, the risk-free rate and the volatility. We wrap them in quotes, so that the instrument will be notified of any changes…
u = ql.SimpleQuote(100.0)
r = ql.SimpleQuote(0.01)
σ = ql.SimpleQuote(0.20)
…and from the quotes we build the flat curves and the process that the engine requires. As explained in another notebook, we build the term structures so that they move with the evaluation date; this will be useful further on.
riskFreeCurve = ql.FlatForward(
0, ql.TARGET(), ql.QuoteHandle(r), ql.Actual360()
)
volatility = ql.BlackConstantVol(
0, ql.TARGET(), ql.QuoteHandle(σ), ql.Actual360()
)
process = ql.BlackScholesProcess(
ql.QuoteHandle(u),
ql.YieldTermStructureHandle(riskFreeCurve),
ql.BlackVolTermStructureHandle(volatility),
)
Finally, we build the engine (the library provides one based on an analytic formula) and set it to the option.
option.setPricingEngine(ql.AnalyticBarrierEngine(process))
Now we can ask the option for its value…
print(option.NPV())
29.249995281633762
…but we’re not so lucky when it comes to Greeks:
print(option.delta())
RuntimeError: delta not provided
The engine doesn’t provide the delta, so asking for it raises an error.
Numerical calculation
What does a quant have to do? We can use numerical differentiation to approximate the Greeks: that is, we can approximate the derivative by calculating the option value for two slightly different values of the underlying and by taking the slope between the resulting points.
The relevant formulas are:
\[\Delta = \frac{P(u_0+h)-P(u_0-h)}{2h} \; \; \; \; \; \; \Gamma = \frac{P(u_0+h)-2P(u_0)+P(u_0-h)}{h^2}\]where $P(u)$ is the price of the option for a given value of the underlying $u$, $u_0$ is the current value of the underlying and $h$ is a tiny increment.
Thanks to the framework we set in place, getting the perturbed prices is easy enough: we can set the relevant quote to the new value and ask the option for its price again. Thus, we choose a value for $h$ and start. First, we save the current value of the option…
u0 = u.value()
h = 0.01
P0 = option.NPV()
print(P0)
29.249995281633762
…then we increase the underlying value and get the new option value…
u.setValue(u0 + h)
P_plus = option.NPV()
print(P_plus)
29.24851583696518
…then we do the same after decreasing the underlying value.
u.setValue(u0 - h)
P_minus = option.NPV()
print(P_minus)
29.25147220877793
Finally, we set the underlying value back to its current value.
u.setValue(u0)
Applying the formulas above give us the desired Greeks:
Δ = (P_plus - P_minus) / (2 * h)
Γ = (P_plus - 2 * P0 + P_minus) / (h * h)
print(Δ)
print(Γ)
-0.14781859063752734
-0.025175244147135345
The approach is usable for any Greek. We can use the two-sided formula above, or the one-sided formula below if we want to minimize the number of evaluations:
\[\frac{\partial P}{\partial x} = \frac{P(x_0+h)-P(x_0)}{h}\]For instance, here we calculate Rho and Vega:
r0 = r.value()
h = 0.0001
r.setValue(r0 + h)
P_plus = option.NPV()
r.setValue(r0)
ρ = (P_plus - P0) / h
print(ρ)
-9.984440333568045
σ0 = σ.value()
h = 0.0001
σ.setValue(σ0 + h)
P_plus = option.NPV()
σ.setValue(σ0)
Vega = (P_plus - P0) / h
print(Vega)
-12.997818541293782
The approach for the Theta is a bit different, although it still relies on the fact that the option reacts to the change in the market data. The difference comes from the fact that we don’t have the time to maturity available as a quote, as was the case for the other quantities. Instead, since we set up the term structures so that they move with the evaluation date, we can set it to tomorrow’s date to get the corresponding option value. In this case, the value of the time increment $h$ should be equivalent to one day:
ql.Settings.instance().evaluationDate = today + 1
P1 = option.NPV()
ql.Settings.instance().evaluationDate = today
h = 1.0 / 365
Θ = (P1 - P0) / h
print(Θ)
5.5489988905623555
That’s all for this post. See you next time!