Speeding Up QuantLib Swap Valuations Across Evaluation Dates
Summary
The document diagnoses slow repeated valuation of an overnight indexed swap in QuantLib while advancing the evaluation date and rebuilding curves. The accepted answer identifies evaluation-date changes as the main source of the slowdown: QuantLib refreshes instantiated objects linked to that date. In the reported setup, many prebuilt curve helpers were also being refreshed, multiplying the work at each step.
The proposed remedy is to limit the number of live objects during the loop. The answer describes creating the date-specific curve, yield-curve handle, discounting engine, index, and swap as needed for a valuation, then allowing temporary objects to be discarded instead of keeping many helpers and related objects instantiated. This reduced the unnecessary refresh work, though the author notes that bootstrapping a curve at each step still takes time. The account is a practical explanation tied to one Python and QuantLib workflow; it does not provide benchmark comparisons or establish that the same cause dominates every slow valuation process.
Key ideas
- Changing QuantLib’s evaluation date can trigger updates across instantiated date-dependent objects.
- Prebuilding many curve helpers may make repeated date changes especially expensive.
- Keeping objects transient within the valuation loop can reduce the number of objects requiring refresh.
- Rebuilding a curve at each step still has a computational cost after the object-lifecycle change.
- The explanation comes from one workflow and does not include performance benchmarks.
Tags
Full text
# Quantlib Slow valuation of ois_swap on multiple eval days
# Quantlib Slow valuation of ois_swap on multiple eval days
I have bootstrapped a curve using several depo and swap rates and am trying to use that curve to get the NPV of a swap over a period of time. The generation of prices iteratively through time is incredibly slow. Given that i'm only pricing over a 4 month period I wouldn't expect it to take 30 minutes. Am I doing something silly here? I saw a previous post commenting on a bug in the SWIG wrapper from python to c++, but according to one of the project maintainers it was patched years ago. See the below:
```
test_curve.referenceDate() == Date(4,1,2023)
```
Engine creation
```
ts = ql.RelinkableYieldTermStructureHandle()
yts.linkTo(test_curve)
engine = ql.DiscountingSwapEngine(yts)
```
Swap Definition and Creation
```
swapTenor = ql.Period('1Y')
overnightIndex = ql.Sofr(yts)
fixedRate = 0.01
ois_swap = ql.MakeOIS(swapTenor, overnightIndex, fixedRate, pricingEngine=engine, discountingTermStructure=yts)
```
NPV Generation
```
new_prices = []
instance = ql.Settings.instance()
start_date = ql.Date(1,1,2024)
success_counter = 0
while date < start_date:
# Update eval date in sim
instance.evaluationDate = date
price = ois_swap.NPV()
new_prices.append(price)
# Increment date forward
date += ql.Period('1D')
new_curve = test_model.get_curve_by_date(date.to_date().strftime('%Y-%m-%d'))
count = 0
# Check for new_curve to exist
while new_curve is None:
date += ql.Period('1D')
new_curve = test_model.get_curve_by_date(date.to_date().strftime('%Y-%m-%d'))
count += 1
if count == 100:
break
yts.linkTo(new_curve)
engine = ql.DiscountingSwapEngine(yts)
overnightIndex = ql.Sofr(yts)
ois_swap = ql.MakeOIS(swapTenor, overnightIndex, fixedRate, pricingEngine=engine, discountingTermStructure=yts, effectiveDate=ql.Date(2,1,2024))
```
The maturity date on the swap is set to May 2024. Thanks!
## Answer by StormsEdge (score 3, accepted)
https://quant.stackexchange.com/a/75511
After reading the documentation more closely and some scenario testing, I was able to determine that `ql.Settings.instance().evaluationDate = date` was the culprit. It seems that updating the `evaluationdate` causes a refresh of ALL instantiated objects within QuantLib that are related to that evaluationDate. I had instantiated a dataframe within my `test_model` class and pre-built all of the bootstrapped curves I was intending to use, which resulted in the creation of many swap and depo helper objects, all of which would be updated on the `evaluationDate` change.
For added color: Python destroys objects when their reference counter reaches 0. So, I simply made all of these objects transient within event loop so that I am incrementing, at most, 1 curve and 1 swap object at each step forward. The solution is not lightning fast, since I am bootstrapping a curve at each step, but it's working.
```
while date < effectiveDate:
instance.evaluationDate = date
curve = bootstrap_model.get_curve_by_date(date.to_date().strftime('%Y-%m-%d'), depo=True, swaps=True)
if curve is None:
date += ql.Period('1D')
else:
yts = ql.RelinkableYieldTermStructureHandle()
yts.linkTo(curve)
engine = ql.DiscountingSwapEngine(yts)
overnightIndex = ql.Sofr(yts)
ois_swap = ql.MakeOIS(swapTenor, overnightIndex, fixedRate, pricingEngine=engine, discountingTermStructure=yts, effectiveDate=effectiveDate)
swap_price = ois_swap.NPV()
new_prices.append(swap_price)
```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.