Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions tf_quant_finance/volatility/bachelier_tf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the functions to vanilla_prices module

"""
""
Created on Fri Nov 22 15:22:13 2019

'Copyright 2020 Joerg Kienitz

'Redistribution and use in source and binary forms, with or without modification,
'are permitted provided that the following conditions are met:

'1. Redistributions of source code must retain the above copyright notice,
'this list of conditions and the following disclaimer.

'2. Redistributions in binary form must reproduce the above copyright notice,
'this list of conditions and the following disclaimer in the documentation
'and/or other materials provided with the distribution.

'3. Neither the name of the copyright holder nor the names of its contributors
'may be used to endorse or promote products derived from this software without
'specific prior written permission.

'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
'"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
'THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
'ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
'FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
'(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
'ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
'OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
'THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@author: Joerg Kienitz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Joerg,

Could you please use the Apache 2.0 license instead?
See, e.g., here
This change will be attributed to you.

"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are using Python3 only, these are not needed. Please remove


import tensorflow as tf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import tensorflow.compat.v2 as tf

This is because internally we default to TensorFlow 1

import tensorflow_probability as tfp

# straight fwd implementation of the Bachelier pricing
# there is a version with just one call to exp !!


def option_price(spots,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call it bachelier_option_price

strikes,
volatilities,
expiries,
rates,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are adding rates to black-scholes option price formula. Let's keep the names consistent.
Please call it discount_rates and also add discount_factors. Please raise an error if both supplied at the same time.

is_call_options=None,
dtype = None,
name = None):
""" Compute the Bachelier price for a batch of European options.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one-line docstring should start with an imperative verb, e.g., 'Computes' here. Please fix for other functions too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short note explaining that Bachelier formula comes if one assumes Brownian motion behavior instead of geometric bromwnian motion


## References:
[1] Kienitz, J. "Interest Rate Derivatives Explained I", Plagrave McMillan (2014) p.119
[2] https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3428994
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please wright a full reference and add a link. See, e.g., here


Parameters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few notes here to make the code follow the style guide. Please remove --- everywhere.
Parameters is called Args: followed by semicolon. Example should be moved before Args: Please find an example here

----------
spots : A real `Tensor` of any shape. The current spot prices to
expiry.
strikes : A real `Tensor` of the same shape and dtype as `spots`. The
strikes of the options to be priced.
volatilities : A real `Tensor` of same shape and dtype as `spots`. The
volatility to expiry.
expiries : A real `Tensor` of same shape and dtype as `spots`. The expiry
for each option. The units should be such that `expiry * volatility**2` is
dimensionless.
rates : A real `Tensor` of the same shape and dtype as `spots`. The
rates of the options to be priced.
is_call_options: A boolean `Tensor` of a shape compatible with `forwards`.
Indicates whether to compute the price of a call (if True) or a put (if
False). If not supplied, it is assumed that every element is a call.
dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion
of any supplied non-`Tensor` arguments to `Tensor`.
Default value: None which maps to the default dtype inferred by TensorFlow
(float32).
name: str. The name for the ops created by this function.
Default value: None which is mapped to the default name `option_price`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe default to bachelier_option_price?


Returns
-------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please remove ---

option_prices: A `Tensor` of the same shape as `spots`. The Bachelier
price of the options.

#### Examples
```python
spots = np.array([0.03, 0.02])
strikes = np.array([.02, .02])
volatilities = np.array([.004, .005])
expiries = 2.0
rates = [0.02, 0.01]
computed_prices = option_price(
spots,
strikes,
volatilities,
expiries,
rates,
dtype=tf.float64)
# Expected print output of computed prices:
# <tf.Tensor: id=2482, shape=(2,), dtype=float32, numpy=array([0.01605039, 0.00720789], dtype=float32)>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This output is suspicious as we would expect float64.
Also, let's leave only [0.01605039, 0.00720789] since some users might be using TensorFlow 1 and which means they need to follow extra steps before getting the result

```

"""
with tf.compat.v1.name_scope(
name,
default_name='option_price',
values=[
spots, strikes, volatilities, expiries, rates,
is_call_options
]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead
with tf.name_scope(name or 'bachelier_option_price')


spots = tf.convert_to_tensor(spots, dtype=tf.float64, name='forwards')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the pattern here

spots = tf.convert_to_tensor(spots, dtype=dtype, name='forwards')
dtype = spots.dtype
...

strikes = tf.convert_to_tensor(strikes, dtype=tf.float64, name='strikes')
volatilities = tf.convert_to_tensor(volatilities, tf.float64, name='volatilities')
expiries = tf.convert_to_tensor(expiries, tf.float64, name='expiries')
rates = tf.convert_to_tensor(rates, tf.float64, name='rates')

z = tf.zeros_like(strikes)

normal = tfp.distributions.Normal(
loc=tf.zeros([], dtype=spots.dtype), scale=1)

df = tf.math.exp(-rates*expiries)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces around operations -rates * expiries

vt = volatilities * tf.math.sqrt(expiries)

z = tf.where(rates == 0., (spots - strikes)/vt,
(spots-strikes*df)/(volatilities
* tf.math.sqrt(0.5*(1.-tf.math.exp(-2.*rates*expiries))/rates)))

n1 = normal.cdf(z)
n2 = normal.prob(z)
calls = tf.where(rates==0., (spots - strikes) * n1 + vt * n2,
(spots - strikes*df)*n1
+ volatilities*tf.math.sqrt(0.5*(1-tf.math.exp(-2*rates*expiries))/rates))


if is_call_options is None:
return calls

puts = calls - spots + strikes * tf.math.exp(-rates*expiries)

return tf.where(is_call_options, calls, puts)



def option_price_dawson_tf(forwards,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dawson_option_price

strikes,
volatilities,
expiries,
discount_factors = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, add discount_rates

is_call_options=None,
dtype = None,
name = None):

"""Computes the Black Scholes price for a batch of European options.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please clarify what assumptions does this option price entails?


## References:
[1] Dawson, P., Blake, D., Cairns, A. J. G. and Dowd, K.: Options on normal under-
lyings, CRIS Discussion Paper Series – 2007.VII, 2007.

Args:
forwards: A real `Tensor` of any shape. The current forward prices to
expiry.
strikes: A real `Tensor` of the same shape and dtype as `forwards`. The
strikes of the options to be priced.
volatilities: A real `Tensor` of same shape and dtype as `forwards`. The
volatility to expiry.
expiries: A real `Tensor` of same shape and dtype as `forwards`. The expiry
for each option. The units should be such that `expiry * volatility**2` is
dimensionless.
discount_factors: A real `Tensor` of same shape and dtype as the `forwards`.
The discount factors to expiry (i.e. e^(-rT)). If not specified, no
discounting is applied (i.e. the undiscounted option price is returned).
Default value: None, interpreted as discount factors = 1.
is_call_options: A boolean `Tensor` of a shape compatible with `forwards`.
Indicates whether to compute the price of a call (if True) or a put (if
False). If not supplied, it is assumed that every element is a call.
dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion
of any supplied non-`Tensor` arguments to `Tensor`.
Default value: None which maps to the default dtype inferred by TensorFlow
(float32).
name: str. The name for the ops created by this function.
Default value: None which is mapped to the default name `option_price`.

Returns:
option_prices: A `Tensor` of the same shape as `forwards`. The Bachelier
price of the options.


#### Examples
```python
spots = np.array([0.03, 0.02])
strikes = np.array([.02, .02])
volatilities = np.array([.004, .005])
expiries = 2.0
expiries = 1.0
computed_prices = option_price(
forwards,
strikes,
volatilities,
expiries,
dtype=tf.float64)
# Expected print output of computed prices:
# <tf.Tensor: id=2527, shape=(2,), dtype=float32, numpy=array([0.01008754, 0.00282095], dtype=float32)>
```
"""
with tf.compat.v1.name_scope(
name,
default_name='option_price_dawson',
values=[
forwards, strikes, volatilities, expiries, discount_factors,
is_call_options
]):

forwards = tf.convert_to_tensor(forwards, dtype=None, name='forwards')
strikes = tf.convert_to_tensor(strikes, dtype=None, name='strikes')
volatilities = tf.convert_to_tensor(volatilities, dtype=None, name='volatilities')
expiries = tf.convert_to_tensor(expiries, dtype=None, name='expiries')

if discount_factors is None:
discount_factors = 1.
discount_factors = tf.convert_to_tensor(
discount_factors, dtype=dtype, name='discount_factors')

vt = volatilities * tf.math.sqrt(expiries)
normal = tfp.distributions.Normal(
loc=tf.zeros([], dtype=forwards.dtype), scale=1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use tensorflow error function. See, e.g., here


z = (forwards - strikes) / vt

n1 = normal.cdf(z)
n2 = normal.prob(z)
undiscounted_calls = (forwards-strikes) * n1 + vt * n2

if is_call_options is None:
return discount_factors * undiscounted_calls
undiscounted_forward = forwards - strikes
undiscounted_puts = undiscounted_calls - undiscounted_forward

return discount_factors * tf.where(is_call_options, undiscounted_calls,
undiscounted_puts)



Loading