The Life Modeling Problem
Inspired by the discussion in the ActuarialOpenSource GitHub community discussion, folks started submitted solutions to what someone referred to as the “Life Modeling Problem”. This user submitted a short snippet for consideration of a representative problem.
Benchmarks
After the original user submitted a proposal, others chimed in and submitted versions in their favorite languages. I have collected those versions, and run them on a consistent set of hardware.
Some submissions were excluded because from the benchmarks they involved an entirely different approach, such as memoizing the function calls[^1].
[ Info: Precompiling DataFrames [a93c6f00-e57d-5684-b7b6-d8193f3e46c0] (cache misses: wrong dep version loaded (10), invalid header (10))
To aid in visualizing results with such vast different orders of magnitude, this graph includes a physical length comparison to serve as a reference. The computation time is represented by the distance that light travels in the time for the computation to complete (comparing a nanosecond to one foot length goes at least back to Admiral Grace Hopper).
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80] (cache misses: wrong dep version loaded (4), invalid header (12))
[ Info: Precompiling IJuliaExt [2f4121a4-3b3a-5ce6-9c5e-1f2673ce168a] (cache misses: wrong dep version loaded (2), invalid header (2))
Discussion
For more a more in-depth discussion of these results, see this post.
All of the benchmarked code can be found in the JuliaActuary Learn repository. Please file an issue or submit a PR request there for issues/suggestions.
IRRs
Task: determine the IRR for a series of cashflows 701 elements long (e.g. monthly cashflows for 60 years).
Benchmarks
Times are in nanoseconds:
┌──────────┬──────────────────┬───────────────────┬─────────┬─────────────┬───────────────┐
│ Language │ Package │ Function │ Median │ Mean │ Relative Mean │
├──────────┼──────────────────┼───────────────────┼─────────┼─────────────┼───────────────┤
│ Python │ numpy_financial │ irr │ missing │ 519306422 │ 123146x │
│ Python │ better │ irr_binary_search │ missing │ 3045229 │ 722x │
│ Python │ better │ irr_newton │ missing │ 382166 │ 91x │
│ Julia │ ActuaryUtilities │ irr │ 4185 │ 4217 │ 1x │
└──────────┴──────────────────┴───────────────────┴─────────┴─────────────┴───────────────┘
Discussion
The ActuaryUtilities implementation is over 100,000 times faster than numpy_financial
, and 91 to 722 times faster than the better
Python package. The ActuaryUtilities.jl implementation is also more flexible, as it can be given an argument with timepoints, similar to Excel’s XIRR
.
Excel was used to attempt a benchmark, but the IRR
formula returned a #DIV/0!
error.
All of the benchmarked code can be found in the JuliaActuary Learn repository. Please file an issue or submit a PR request there for issues/suggestions.
Black-Scholes-Merton European Option Pricing
Task: calculate the price of a vanilla european call option using the Black-Scholes-Merton formula.
\[\begin{align}
C(S_t, t) &= N(d_1)S_t - N(d_2)Ke^{-r(T - t)} \\
d_1 &= \frac{1}{\sigma\sqrt{T - t}}\left[\ln\left(\frac{S_t}{K}\right) + \left(r + \frac{\sigma^2}{2}\right)(T - t)\right] \\
d_2 &= d_1 - \sigma\sqrt{T - t}
\end{align}\]
Benchmarks
Times are in nanoseconds:
┌──────────┬─────────┬─────────────┬───────────────┐
│ Language │ Median │ Mean │ Relative Mean │
├──────────┼─────────┼─────────────┼───────────────┤
│ Python │ missing │ 817000.0 │ 19926.0 │
│ R │ 3649.0 │ 3855.2 │ 92.7 │
│ Julia │ 41.0 │ 41.6 │ 1.0 │
└──────────┴─────────┴─────────────┴───────────────┘
Discussion
Julia is nearly 20,000 times faster than Python, and two orders of magnitude faster than R.
Other benchmarks
These benchmarks have been performed by others, but provide relevant information for actuarial-related work:
Colophone
Code
All of the benchmarked code can be found in the JuliaActuary Learn repository. Please file an issue or submit a PR request there for issues/suggestions.