Why QuantLib Heston Pricing Can Be Slower Than a Specialized Formula
Summary
The question compares a customized Heston pricing implementation with QuantLib’s analytic engine and asks whether the library is unusually slow. The reported timing is for pricing many options with differing strikes under a flat-rate setup, using a cosine expansion with additional truncation of its summation. These figures are the questioner’s benchmark, not an independent performance study.
The answer explains that QuantLib includes general-purpose infrastructure, such as constructing yield curves and extracting maturity-specific rates, which adds overhead compared with code that directly uses a known flat rate. That flexibility becomes useful when rates come from a bootstrapped curve and differ by maturity. For a batch sharing an underlying and market setup, the answer also recommends reusing the curves, process, model, and pricing engine rather than recreating them for each option. The comparison may depend on what setup work is included in the timing and how objects are reused.
Key ideas
- General-purpose curve and model infrastructure adds overhead compared with a specialized formula using direct inputs.
- QuantLib can extract maturity-specific rates from a term structure, which is useful for non-flat market curves.
- A batch of options sharing market inputs can reuse the same pricing setup.
- Reported benchmark timings depend on the tested setup and object-reuse pattern.
Tags
Full text
# Wrt speed, how optimised is QuantLib's Heston pricing class? # Wrt speed, how optimised is QuantLib's Heston pricing class? I have a pricing formula that is 300x the speed of the QuantLib's Heston pricing class. Is it incredibly slow? For context, on a slow 1.6 GHz Dual-Core Intel Core i5 processor, my method can reliably price 1000 different options in 0.005 seconds whilst it takes the QuantLib's 1.5 seconds. And what method/integration are they using? I cannot find it on their documentation or in their code anywhere. It uses the C++ library if I understand correctly, and I know 0 C++ I am using cosine expansion, the method can be found here. But I am have made my own adjustments to truncate even more terms of the summation. From the QL library, I have been using the code: ``` strikes = np.arange(50, 200, 0.01) today = ql.Date(26, 10, 2023) expiry_date = today + ql.Period(int(365*tau), ql.Days) risk_free_curve = ql.FlatForward(today, r, ql.Actual365Fixed()) flat_curve = ql.FlatForward(today, 0.0, ql.Actual365Fixed()) riskfree_ts = ql.YieldTermStructureHandle(risk_free_curve) dividend_ts = ql.YieldTermStructureHandle(flat_curve) heston_process = ql.HestonProcess(riskfree_ts, dividend_ts, ql.QuoteHandle(ql.SimpleQuote(S0)), v0, kappa, theta, sigma, rho) heston_model = ql.HestonModel(heston_process) option = ql.EuropeanOption( ql.PlainVanillaPayoff(ql.Option.Call, strike), ql.EuropeanExercise(expiry_date)) heston_engine = ql.AnalyticHestonEngine(heston_model) option.setPricingEngine(heston_engine) option_price = option.NPV() ``` And then just using `time.time()` to test the speed. ## Answer by Luigi Ballabio (score 5) https://quant.stackexchange.com/a/77603 QuantLib does a lot of things behind the scenes that provide convenient functionality but get in the way of pure speed. For instance, in your example code, when you write ``` risk_free_curve = ql.FlatForward(today, r, ql.Actual365Fixed()) ``` you're building a whole term structure of interest rates, from which the pricing code will extract a zero rate in order to pass it to the Heston formula. In your case, this is obviously wasteful; it will retrieve the same rate `r` that you passed. And your code, instead, probably uses `r` directly and avoids all these calculations and function calls, and therefore is a lot faster. But in a real world case, the risk-free curve would be (for instance) bootstrapped on a set of OIS swaps, and in this case QuantLib becomes more convenient because if you have a set of options, you can pass the curve and let the library extract the correct zero rate for each option based on its maturity. The same goes for a number of other features: they are convenient for a number of common use cases of the library, but the added infrastructure means that the code has no hope to be as fast as a bare-bones implementation of the model. One last thing: I'm not sure how you loop over the options, but if you have (as it seems from `strikes = np.arange(50, 200, 0.01)`) a set of options on the same underlying and with different strikes, you can instantiate the curves, the process, the model and the pricing engine just once and pass the same pricing engine to all the options. I'm not sure if you're already doing this when you're timing QuantLib, of if you're recreating those objects each time.
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.