Uwe Naumann, RWTH Aachen University, Germany
In an ideal world, all quant libraries would provide a complete collection of the state of the art in mathematical finance implemented as easy-to-use, efficient, scalable and sustainable software... and corresponding adjoints! – Uwe Naumann, Professor of Computer Science, RWTH Aachen University, added. The challenges in this area can be plentiful, but the solutions can be simple.
Numerical simulation in finance (and elsewhere) relies on a hierarchy of hopefully highly optimised software components including modules for model calibration, Monte Carlo simulation and solvers for partial differential equations provided in form of quant libraries. Scenarios of interest such as specific payoffs are often synthesised from these fundamental components using a set of both syntactically and semantically relatively simple instructions. The latter typically do not require the same syntactic richness and complexity exhibited by general-purpose programming languages. (Domain-specific) Scripting languages tend to be the method of choice. They should enforce differentiability. Their users should not be required to have the same level of software development expertise as their colleagues who implement the core quant library.
Derivatives (Greeks) play a fundamental role in the quantification of errors and risk as well as in model-based predictions of the uncertain future. Consequently, algorithmic differentiation (AD) has evolved into the quasi-standard for computing derivatives, in particular adjoints, of financial models over the past decade. Combinations of manual and/or automatic source-to-source transformation and operator overloading / template metaprogramming methods have been employed successfully across the industry. A prominent example is Scotiabank's XVA engine honoured by Risk.net's 2021 Technology Innovation of the Year Award.
Ideally, an existing quant library comes with highly optimised adjoint components. Given such components the missing link toward a fully adjoint application is the integration of the part of the software that is written in the given scripting language. Again, all methods known from AD can be employed. The main challenge of automatic source-to-source transformation is the complexity of general-purpose programming languages as well as the dynamics of their evolution. For example, to date there is no fully operational source-to-source transformation AD tool for C++. Operator overloading in combination with template metaprogramming has become the method of choice due to its robustness with respect to changes in the core language and to extensions of the C++ Standard Library.
Source-to-source transformation reenters the stage when considering scripting languages. Their automatic transformation into adjoint code can be formalised by attribute grammars. Single-pass syntax-directed translation into adjoints becomes possible. Adjoint code is emitted while parsing the original script. More sophisticated static programme analysis and optimisation would require construction of an intermediate representation. This is probably not necessary as the scripted part of the software is not likely to represent the bottleneck in terms of computational cost. (If it does, then reimplementation in C++ and parallelisation should be considered.) An adjoint script calling highly optimised adjoint quant library components can be generated rather easily.
The Monte Carlo simulation (mc) is implemented in C++. It is linked with the Python script through pybind11. Discounting is part of the black_scholes_call function. The adjoint Monte Carlo simulation can be generated with an adjoint AD tool such as NAG's dco/c++. It is called by the automatically generated adjoint script.
As a generic “playground” for discussing adjoint code generation rules and their automatic application in the context of a source-to-source compiler this example offers plenty of room for improvement. First, it could be written in a more compact form using the maximum function. In any case, the payoff is obviously not differentiable at x[3]=s[i]. One might want to smoothen it, e.g., using a sigmoidal approximation provided by a custom runtime library. Alternatively, a local bump could be used.
For a scripting language to be differentiable it must not support active branches; AD4SL can prevent their use. Here, activity describes dependence of the branch (or loop termination) condition on active values. A value is called active if it depends on x as well as having an impact on y in the context of differentiation of y with respect to x. Both x[3] and s[i] are active in the above example. Moreover, all intrinsics and callable library methods must be differentiable. Consequently, the burden of ensuring differentiability is shifted primarily to the design of the runtime library. The scripting language itself remains relatively simple. Users can adapt the AD4SL compiler to their specific needs through potential extension of the target language.
In some cases, differentiability of the whole programme does not require strict local differentiability. However, such situations are difficult to formally be captured from the language design perspective. They should be exploited during the implementation of the runtime library. A nondifferentiable scripting language can of course be used to implement differentiable scripts. However, in this case differentiability is not guaranteed by the language itself.
I look forward to further fruitful discussions about this subject (as well as many others) at the upcoming QuantMinds International conference.
Find out more