# Chapter 6, part 3 of 8: the Black-Scholes process

Welcome back.

This post is the third in a series (started here) that covers chapter 6 of my book. This time, an example: the Black-Scholes process (in which I’ll point out all the flaws in our code, thus showing that I’m not cut out for marketing).

You still have a few days (until the end of this month) to get an
early-bird discount for my *Introduction to QuantLib Development*
course,
which will be on September 22nd to 24th in London. I’ve talked about
it at length in a previous post, so I’ll shut up
now.

Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the buttons for that are in the footer. Also, make sure to check my Training page.

### Example: the Black-Scholes process

And now, an example or three. Not surprisingly, the first is the
one-dimensional Black-Scholes process. You’ve already met briefly the
corresponding class (by the unwieldy name of
`GeneralizedBlackScholesProcess`

) in this post, where it was passed as
an argument to a pricing engine for a European option; I’ll go back to
that code later on, when I discuss a few shortcomings of the current
design and possible future solutions. For the time being, let’s have a
look at the current implementation, sketched in listing 6.6.

Listing 6.6: Partial implementation of the
`GeneralizedBlackScholesProcess`

class.

Well, first of all, I should probably explain the length of the class name. The “generalized” bit doesn’t refer to some extension of the model, but to the fact that this class was meant to cover different specific processes (such as the Black-Scholes process proper, the Black-Scholes-Merton process, the Black process and so on); the shorter names were reserved for the specializations.

The constructor of this class takes handles to the full set of
arguments needed for the generic formulation: a term structure for the
risk-free rate, a quote for the varying market value of the
underlying, another term structure for its dividend yield, and a term
structure for its implied Black volatility. In order to implement the
`StochasticProcess`

interface, the constructor also takes a
`discretization`

instance. All finance-related inputs are stored in
the constructed class instance and can be retrieved by means of
inspectors.

You might have noticed that the constructor takes a Black volatility \( \sigma_B(t,k) \), which is not what is needed to return the \( \sigma(t,x) \) diffusion term. The process performs the required conversion internally, using different algorithms depending on the type of the passed Black volatility (constant, depending on time only, or with a smile). The type is checked at run-time with a dynamic cast, which is not pretty but in this case is simpler than a Visitor pattern. At this time, the conversion algorithms are built into the process and can’t be changed by the user; nor it is possible to provide a precomputed local volatility. The class uses the Observer pattern to determine when to convert the Black volatility into the local one. Recalculation is lazy; meaning that it happens only when the local volatility is actually used and not immediately upon a notification from an observable.

The next methods, `drift`

and `diffusion`

, show the crux of this
class: it’s actually a process for the logarithm of the underlying
that masquerades as a process for the underlying itself. Accordingly,
the term returned by `drift`

is the one for the logarithm, namely, \(
r(t) - q(t) - \frac{1}{2}\sigma^2(t,x) \), where the rates and the
local volatility are retrieved from the corresponding term structures
(this is another problem in itself, to which I’ll return). The
`diffusion`

method returns the local volatility.

The choice of \( \log(x) \) as the actual stochastic variable brings
quite a few consequences. First of all, the variable we’re using is
not the one we’re exposing. The quote we pass to the constructor holds
the market value of the underlying; the same value is returned by the
`x0`

method; and the values passed to and returned from other methods
such as `evolve`

are for the same quantity. In retrospect, this was a
bad idea (yes, I can hear you say “D’oh.”) We were led to it by the
design we had developed, but it should have been a hint that some
piece was missing.

Other consequences can be seen all over the listing. The choice of \(
\log(x) \) as the working variable is the reason why the `apply`

method is virtual, and indeed why it exists at all: applying to \( x
\) a drift \( \Delta \) meant for \( log(x) \) is not done by
adding them, but by returning \( x\exp(\Delta) \). The `expectation`

method is affected, too: its default implementation would apply the
drift as above, thus returning \( \exp(E[\log(x)]) \) instead of \(
E[x] \). To prevent it from returning the wrong value, the method is
currently disabled (it raises an exception). In turn, the `evolve`

method, whose implementation relied on `expectation`

, had to be
overridden to work around it. (The `evolve`

method might have been
overridden for efficiency, even if `expectation`

were available. The
default implementation in `StochasticProcess1D`

would return \(
x\exp(\mu dt)\exp(\sigma dw) \), while the overridden one calculates
\( x\exp(\mu dt + \sigma dw) \).)

On top of the above smells, we have a performance problem—which
is known to all those that tried a Monte Carlo engine from the
library, as well as to all the people on the Wilmott forums to whom
the results were colorfully described. Using `apply`

, the `evolve`

method performs an exponentiation at each step; but above all, the
`drift`

and `diffusion`

methods repeatedly ask term structures for
values. If you remember this post, this means going
through at least a couple of levels of virtual method calls, sometimes
(if we’re particularly unlucky) retrieving a rate from discount
factors that were obtained from the same rate to begin with.

Finally, there’s another design problem. As I mentioned previously, we
already used the Black-Scholes process in the `AnalyticEuropeanEngine`

shown as an example in this post. However, if you look at
the engine code in the library, you’ll see that it doesn’t use the
process by means of the `StochasticProcess`

interface; it just uses it
as a bag of quotes and term structures. This suggests that the process
is a jack of too many trades.

How can we fix it, then? We’ll probably need a redesign. It’s all hypothetical, of course; there’s no actual code yet, just a few thoughts I had while I was reviewing the current code for this chapter. But it could go as follows.

First of all (and at the risk of being told once again that I’m
overengineering) I’d separate the stochastic-process part of the class
from the bag-of-term-structures part. For the second one, I’d create a
new class, probably called something like
`BlackScholesModel`

. Instances of this class would be the ones passed
to pricing engines, and would contain the full set of quotes and term
structures; while we’re at it, the interface should also allow the
user to provide a local volatility, or to specify new ways to convert
the Black volatility with some kind of Strategy pattern.

From the model, we could build processes. Depending on the level of coupling and the number of classes we prefer, we might write factory classes that take a model and return processes; we might add factory methods to the model class that return processes; or we might have the process constructors take a model. In any case, this could allow us to write process implementations that could be optimized for the given simulation. For instance, the factory (whatever it might be) might take as input the time nodes of the simulation and return a process that precomputed the rates \( r(t_i) \) and \( q(t_i) \) at those times, since they don’t depend on the underlying value; if a type check told the factory that the volatility doesn’t have smile, the process could precompute the \( \sigma(t_i) \), too.

As for the \( x \) vs \( \log(x) \) problem, I’d probably rewrite
the process so that it’s explicitly a process for the logarithm of the
underlying: the `x0`

method would return a logarithm, and so would the
other methods such as `expectation`

or `evolve`

. The process would
gain in consistency and clarity; however, since it would be no longer
guaranteed that a process generates paths for the underlying, client
code taking a generic process would also need a function or a function
object that converts the value of the process variable into a value of
the underlying. Such function might be added to the
`StochasticProcess`

interface, or passed to client code along with the
process.