# Have you tried to calculate derivatives using TensorFlow 2?

### mgreco

#### 13/02/2020

We will learn how to implement a simple function using TensorFlow 2 and how to obtain the derivatives from it. We will implement a Black-Scholes model for pricing a call option and then we are going to obtain the greeks.

Matthias Groncki wrote a very interesting post about how to obtain the greeks of a pricing option using TensorFlow which inspired me to write this post. So, I took the same example and make some updates to use TensorFlow 2.

## Black-Scholes pricing formula

We are going to implement the Black-Scholes formula for pricing options. In this example, we focus on the call option.

Version 2 of TensorFlow has many enhancements, especially on the python API which makes it easier to write code than before.

@tf.function
def pricer_blackScholes(S0, strike, time_to_expiry, implied_vol, riskfree):
"""Prices call option.

Parameters
----------
S0 : float
Spot price.
strike : float
Strike price.
time_to_expiry : float
Time to maturity.
implied_vol : float
Volatility.
riskfree : float
Risk free rate.

Returns
-------
npv : float
Net present value.

Examples
--------
>>> kw = initialize_variables(to_tf=True)
>>> pricer_blackScholes(**kw)

Notes
-----
https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model#Black%E2%80%93Scholes_formula
"""
S       = S0
K       = strike
dt      = time_to_expiry
dt_sqrt = tf.sqrt(dt)
sigma   = implied_vol
r       = riskfree
Phi     = tf.compat.v1.distributions.Normal(0., 1.).cdf

d1 = (tf.math.log(S / K) + (r + sigma ** 2 / 2) * dt) / (sigma * dt_sqrt)
d2 = d1 - sigma * dt_sqrt

npv =  S * Phi(d1) - K * tf.exp(-r * dt) * Phi(d2)
return npv


As we can see the above code is the implementation of a call option in terms of the Black-Scholes framework. A very cool improvement is the tf.function decorator which creates a callable graph for us.

## Calculating Derivatives

In previous versions of TensorFlow, we need to use tf.gradient which requires us to create a session and plenty of annoying stuff. Now, all this process is done using tf.GradientTape which is simpler. We may get it done writing something like :

with tf.GradientTape() as g1:
npv = pricer_blackScholes(**variables)
dv = g1.gradient(npv, variables)  # first order derivatives


Ok, but what if we want higher-order derivatives? The answer is easy, we only have to add a new tf.GradientTape:

with tf.GradientTape() as g2:
npv = pricer_blackScholes(**variables)


### Black-Scholes model

We use the well-known Black-Scholes model to estimate the price of the call. Our code can be written as follows:

@tf.function
def pricer_blackScholes(S0, strike, time_to_expiry, implied_vol, riskfree):
"""pricer_blackScholes.

Parameters
----------
S0 : tensorflow.Variable
Underlying spot price.
strike : tensorflow.Variable
Strike price.
time_to_expiry : tensorflow.Variable
Time to expiry.
implied_vol : tensorflow.Variable
Volatility.
riskfree : tensorflow.Variable
Risk free rate.

Returns
-------
npv : tensorflow.Tensor
Net present value.

Examples
--------
>>> kw = initialize_variables(to_tf=True)
>>> pricer_blackScholes(**kw)
<tf.Tensor: id=120, shape=(), dtype=float32, numpy=9.739834>

Notes
-----
Formula: https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model#Black%E2%80%93Scholes_formula
"""
S       = S0
K       = strike
dt      = time_to_expiry
dt_sqrt = tf.sqrt(dt)
sigma   = implied_vol
r       = riskfree
Phi     = tf.compat.v1.distributions.Normal(0., 1.).cdf

d1 = (tf.math.log(S / K) + (r + sigma ** 2 / 2) * dt) / (sigma * dt_sqrt)
d2 = d1 - sigma * dt_sqrt

npv =  S * Phi(d1) - K * tf.exp(-r * dt) * Phi(d2)
return npv


To get the net present value (NPV) and the greeks (derivatives) we can write a function that wraps all the process. It’s optional of course but very useful.

def calculate_blackScholes():
"""calculate_blackScholes.

Returns
-------
out : dict
npv : net present value
dv : First order derivates

Examples
--------
>>> out = calculate_blackScholes()
>>> pprint(out)
{'dv': {'S0': 0.5066145,
'implied_vol': 56.411205,
'riskfree': 81.843216,
'strike': -0.37201464,
'time_to_expiry': 4.0482087},
'npv': 9.739834}
"""
variables = initialize_variables(to_tf=True)

npv = pricer_blackScholes(**variables)

dv = {k: v.numpy() for k,v in dv.items()}  # get the value
return dict(npv=npv.numpy(), dv=dv)



The previous function returns:

>>> calculate_blackScholes()
{'dv': {'S0': 0.5066145,
'implied_vol': 56.411205,
'riskfree': 81.843216,
'strike': -0.37201464,
'time_to_expiry': 4.048208},
'npv': 9.739834}


Where:

• npv : The net present value is 9.74.
• S0 = $$\frac{\partial v}{\partial S}$$
• implied_vol = $$\frac{\partial v}{\partial \sigma}$$
• strike = $$\frac{\partial v}{\partial K}$$
• time_to_expiry = $$\frac{\partial v}{\partial \tau}$$

We have seen how to implement a TensorFlow function and how to get the derivatives from it. Now, we are going to see another example using the Monte Carlo method.

### Monte Carlo method

The Monte Carlo method is very useful when we don’t have a closed formula or it’s very complex. We are going to implement the Monte Carlo pricing function, for this task I decided to implement a brownian function too, which is used inside of pricer_montecarlo.

@tf.function
def pricer_montecarlo(S0, strike, time_to_expiry, implied_vol, riskfree, dw):
"""Monte Carlo pricing method.

Parameters
----------
S0 : tensorflow.Variable
Underlying spot price.
strike : tensorflow.Variable
Strike price.
time_to_expiry : tensorflow.Variable
Time to expiry.
implied_vol : tensorflow.Variable
Volatility.
riskfree : tensorflow.Variable
Risk free rate.
dw : tensorflow.Variable
Normal random variable.

Returns
-------
npv : tensorflow.Variable
Net present value.

Examples
--------
>>> nsims = 10
>>> nobs = 100
>>> dw = tf.random.normal((nsims, nobs), seed=3232)
>>> v = initialize_variables(to_tf=True)
>>> npv = pricer_montecarlo(**v, dw=dw)
>>> npv
<tf.Tensor: id=646, shape=(), dtype=float32, numpy=28.780073>
"""
sigma = implied_vol
T = time_to_expiry
r = riskfree
K = strike
dt = T / dw.shape[1]

st = brownian(S0, dt, sigma, r, dw)
payout = tf.math.maximum(st[:, -1] - K, 0)
npv = tf.exp(-r * T) * tf.reduce_mean(payout)

return npv

@tf.function
def brownian(S0, dt, sigma, mu, dw):
"""Generates a brownian motion.

Parameters
----------
S0 : tensorflow.Variable
Initial value of Spot.
dt : tensorflow.Variable
Time step.
sigma : tensorflow.Variable
Volatility.
mu : tensorflow.Variable
Mean, in black Scholes frame it's the risk free rate.
dw : tensorflow.Variable
Random variable.

Returns
-------
out : numpy.array

Examples
--------
>>> nsims = 10
>>> nobs = 400
>>> v = initialize_variables(to_tf=True)
>>> S0 = v["S0"]
>>> dw = tf.random.normal((nsims, nobs), seed=SEED)
>>> dt = v["time_to_expiry"] / dw.shape[1]
>>> sigma = v["implied_vol"]
>>> r = v["riskfree"]
>>> paths = np.transpose(brownian(S0, dt, sigma, r, dw))
"""
dt_sqrt = tf.math.sqrt(dt)
shock = sigma * dt_sqrt * dw
drift = (mu - (sigma ** 2) / 2)
bm = tf.math.exp(drift * dt + shock)
out = S0 * tf.math.cumprod(bm, axis=1)
return out


Now, we are ready to calculate the NPV and the greeks under this frame.

def calculate_montecarlo(greeks=True):
"""calculate_montecarlo.

Returns
-------
out : dict
npv : Net present value
dv : First order derivatives
d2v : Second order derivatives

Examples
--------
>>> out = calculate_montecarlo()
>>> pprint(out)
{'dv': {'S0': 0.5065364,
'implied_vol': 56.45906,
'riskfree': 81.81441,
'strike': -0.37188327,
'time_to_expiry': 4.050169},
'npv': 9.746445}
"""
nsims = 10000000
nobs = 2
dw = tf.random.normal((nsims, nobs), seed=SEED)
v = initialize_variables(to_tf=True)

out = dict()

npv = pricer_montecarlo(**v, dw=dw).numpy()

out["dv"] = {k: v.numpy() for k, v in dv.items()}
return out


The output:

>>> out = calculate_montecarlo()
>>> pprint(out)
{'dv': {'S0': 0.5065364,
'implied_vol': 56.45906,
'riskfree': 81.81441,
'strike': -0.37188327,
'time_to_expiry': 4.050169},
'npv': 9.746445}


### Comparison

We are taking a look at the results of both methods:

VariableBlack-ScholesMontecarlo
npv9.7464459.739834
$$\frac{\partial v}{\partial S}$$0.50653640.5066145
$$\frac{\partial v}{\partial \sigma}$$56.4590656.411205
$$\frac{\partial v}{\partial r}$$81.8144181.843216
$$\frac{\partial v}{\partial K}$$-0.37188327-0.37201464
$$\frac{\partial v}{\partial \tau}$$4.0501694.048208

As we can see, we can get similar results with both methods. There is room for improvement, for example, we can increase the number of simulations into the Monte Carlo method. However, the results are reasonably close. Notice that the new version of TensorFlow makes the development process pretty simple which is great news for quants.

Have you ever used this before? It would be great if you can tell your use case in the comments.

Keep coding!