Vanilla Option Pricing¶
A Python package implementing stochastic models to price financial options.
The theoretical background and a comprehensive explanation of models and their parameters can be found is the paper Fast calibration of two-factor models for energy option pricing by Emanuele Fabbiani, Andrea Marziali and Giuseppe De Nicolao, freely available on arXiv.
Installation¶
The preferred way to install the package is using pip.
If your want to modify the code or contribute to the development, feel free to clone the repository and to set up the project on your own machine.
Install from PyPI¶
Install the package (or add it to your requirements.txt
file):
pip install vanilla_option_pricing
Set up the project¶
Clone the repository:
git clone https://github.com/donlelef/vanilla-option-pricing.git
The project uses Poetry as a dependency manager. To get started, follow the official documentation. Then, simply run:
poetry install
Getting started¶
This tutorial shows basic usage of this package.
Creating options¶
Let’s create a sample call option
from datetime import date, datetime, timedelta
from vanilla_option_pricing.option import VanillaOption
from vanilla_option_pricing.models import GeometricBrownianMotion
from vanilla_option_pricing.calibration import ModelCalibration
option = VanillaOption(
spot=100,
strike=101,
dividend=0,
date=datetime.today(),
maturity=datetime.today() + timedelta(days=30),
option_type='c',
price=1,
instrument='TTF'
)
Implied volatility and option pricing¶
We can compute the implied volatility and create a Geometric Brownian Motion model with it. Of course, if now we ask price the option using the Black framework, we’ll get back the initial price.
volatility = option.implied_volatility_of_undiscounted_price
model = GeometricBrownianMotion(volatility)
model_price = model.price_option_black(option)
print(f'Actual price: {option.price}, model price: {model_price}')
Calibrating models¶
We can also try and calibrate the parameters of a model against listed options.
data_set = [
VanillaOption('TTF', 'c', date(2018, 1, 1), 2, 101, 100, date(2018, 2, 1)),
VanillaOption('TTF', 'p', date(2018, 1, 1), 2, 98, 100, date(2018, 2, 1)),
VanillaOption('TTF', 'c', date(2018, 1, 1), 5, 101, 100, date(2018, 5, 31))
]
for o in data_set:
print(f'Implied volatility: {o.implied_volatility_of_undiscounted_price}')
model = GeometricBrownianMotion(0.2)
calibration = ModelCalibration(data_set)
result, tuned_model = calibration.calibrate_model(model)
print(result)
print(f'Calibrated implied volatility: {tuned_model.parameters[0]}')
As we can see, the calibration process takes the implied volatility of the model close to the average of the options it has been trained on.
Models¶
In the context of this package, a model is a stochastic process.
The package APIs offer a simple way of extracting the variance, the standard deviation and the volatility derived from a model at a given time instant.
There are three models currently implemented by this package: a detailed description and further references can be found in the paper Fast calibration of two-factor models for energy option pricing.
Geometric Brownian Motion¶
The celebrated Geometric Brownian Motion process, adopted in the Black and Black-Scholes-Merton frameworks for option pricing.
from datetime import datetime, timedelta
from vanilla_option_pricing.option import VanillaOption
from vanilla_option_pricing.models import GeometricBrownianMotion
option = VanillaOption(
spot=100,
strike=101,
dividend=0,
date=datetime.today(),
maturity=datetime.today() + timedelta(days=30),
option_type='c',
price=1,
instrument='TTF'
)
volatility = option.implied_volatility_of_undiscounted_price
print(f'Option volatility is {volatility}')
gbm_model = GeometricBrownianMotion(volatility)
t = 0.5
print(f'At time t={t} years, volatility is {gbm_model.volatility(t)}, '
f'variance is {gbm_model.variance(t)}, '
f'standard deviation is {gbm_model.standard_deviation(t)}')
Ornstein-Uhlenbeck¶
The Ornstein-Uhlenbeck process, the simplest mean-reverting model, quite popular for energy commodities.
from vanilla_option_pricing.models import OrnsteinUhlenbeck
ou_model = OrnsteinUhlenbeck(
p_0 = 1,
l = 1,
s = volatility
)
print(f'At time t={t} years, volatility is {ou_model.volatility(t)}, '
f'variance is {ou_model.variance(t)}, '
f'standard deviation is {ou_model.standard_deviation(t)}')
Log Mean-Reverting To Generalised Wiener Process¶
One of the most common two-factor, mean-reverting models.
import numpy as np
from vanilla_option_pricing.models import LogMeanRevertingToGeneralisedWienerProcess
lmrgw_model = LogMeanRevertingToGeneralisedWienerProcess(
p_0 = np.eye(2),
l = 100,
s_x = 0.1,
s_y = 0.3
)
print(f'At time t={t} years, volatility is {lmrgw_model.volatility(t)}, '
f'variance is {lmrgw_model.variance(t)}, '
f'standard deviation is {lmrgw_model.standard_deviation(t)}')
Model calibration¶
The market calibration calibration is a procedure which takes an option pricing model and a set of listed vanilla options and tunes the parameters of the model so that the option price given by the model is as close as possible to the actual prices of listed options.
More rigorous details and a mathematical formulation can be found in the paper Fast calibration of two-factor models for energy option pricing.
Creating inputs¶
We’ll suppose that the available dataset to tune our model contains has only three options. In a realistic scenario, tens to hundreds of options would be needed.
from datetime import date
from vanilla_option_pricing.option import VanillaOption
from vanilla_option_pricing.models import GeometricBrownianMotion, OrnsteinUhlenbeck
from vanilla_option_pricing.calibration import ModelCalibration
data_set = [
VanillaOption('TTF', 'c', date(2018, 1, 1), 2, 101, 100, date(2018, 2, 1)),
VanillaOption('TTF', 'p', date(2018, 1, 1), 2, 98, 100, date(2018, 2, 1)),
VanillaOption('TTF', 'c', date(2018, 1, 1), 5, 101, 100, date(2018, 5, 31))
]
We want to calibrate both a Geometric Brownian motion and an Ornstein-Uhlenbeck model.
models = [
GeometricBrownianMotion(0.2),
OrnsteinUhlenbeck(p_0=0, l=100, s=2)
]
Calibrating models¶
We can now instantiate the calibration object, run the optimization algorithm and inspect the results.
calibration = ModelCalibration(data_set)
for model in models:
result, trained_model = calibration.calibrate_model(model)
print('Optimization results:')
print(result)
print(f'Calibrated parameters: {trained_model.parameters}\n\n')
vanilla_option_pricing package¶
vanilla_option_pricing.calibration module¶
-
class
vanilla_option_pricing.calibration.
ModelCalibration
(options: List[vanilla_option_pricing.option.VanillaOption])¶ Bases:
object
Calibrate option pricing models with prices of listed options
Parameters: options – a collection of VanillaOption
-
DEFAULT_PARAMETER_LOWER_BOUND
= 0.0001¶
-
calibrate_model
(model: vanilla_option_pricing.option_pricing.OptionPricingModel, method: str = None, options: Dict[KT, VT] = None, bounds: Union[str, Sequence[Tuple[float, float]]] = 'default') → Tuple[scipy.optimize.optimize.OptimizeResult, vanilla_option_pricing.option_pricing.OptionPricingModel]¶ Tune model parameters and returns a tuned model. The algorithm tries to minimize the squared difference between the prices of listed options and the prices predicted by the model: the parameters of the model are the optimization variables. The numerical optimization is performed by
minimize()
in the scipy package.Parameters: - model – the model to calibrate
- method – see
minimize()
- options – see
minimize()
- bounds – the bounds to apply to parameters. If none is specified, then the
DEFAULT_PARAMETER_LOWER_BOUND
is applied for all the parameters. Otherwise, a list of tuples (lower_bound, upper_bound) for each parameter shall be specified.
Returns: a tuple (res, model), where res is the result of
minimize()
, while model a calibrated model
-
vanilla_option_pricing.models module¶
-
class
vanilla_option_pricing.models.
GeometricBrownianMotion
(s: float)¶ Bases:
vanilla_option_pricing.option_pricing.OptionPricingModel
The celebrated Geometric Brownian Motion model
Parameters: s – the volatility, must be non-negative -
name
= 'Geometric Brownian Motion'¶
-
parameters
¶ Model parameters, as a tuple of real numbers, in the order (s, ).
-
variance
(t: float) → float¶ The variance of the model output at a given time instant
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
-
-
class
vanilla_option_pricing.models.
LogMeanRevertingToGeneralisedWienerProcess
(p_0: numpy.array, l: float, s_x: float, s_y: float)¶ Bases:
vanilla_option_pricing.option_pricing.OptionPricingModel
The Log Mean-Reverting To Generalised Wiener Process model. It is a two-factor, mean reverting model, where the long-term behaviour is given by a Geometric Brownian motion, while the short-term mean-reverting tendency is modelled by an Ornstein-Uhlenbeck process.
Parameters: - p_0 – the initial variance, that is the variance of the state at time t=0. Must be a 2x2 numpy array
- l – the strength of mean-reversion, must be non-negative
- s_x – volatility of the long-term process, must be non-negative
- s_y – volatility of the short-term process, must be non-negative
-
name
= 'Log Mean-Reverting To Generalised Wiener Process'¶
-
parameters
¶ Model parameters, as a tuple of real numbers, in the order l, s_x, s_y.
-
variance
(t: float) → float¶ The variance of the model output at a given time instant
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
-
class
vanilla_option_pricing.models.
NumericalLogMeanRevertingToGeneralisedWienerProcess
(p_0: numpy.array, l: float, s_x: float, s_y: float)¶ Bases:
vanilla_option_pricing.option_pricing.OptionPricingModel
This model relies on the same stochastic process as
LogMeanRevertingToGeneralisedWienerProcess
, but uses a numerical procedures based on a matrix exponential instead of the analytical formulas to compute the variance. As this approach is considerably slower, it is strongly suggested to adoptLogMeanRevertingToGeneralisedWienerProcess
instead, using this class only for benchmarkingParameters: - p_0 – the initial variance, that is the variance of the state at time t=0. Must be a 2x2 numpy array, symmetric and positive semidefinite
- l – the strength of mean-reversion, must be non-negative
- s_x – volatility of the long-term process, must be non-negative
- s_y – volatility of the short-term process, must be non-negative
-
name
= 'Numerical Log Mean-Reverting To Generalised Wiener Process'¶
-
parameters
¶ Model parameters, as a tuple of real numbers, in the order l, s_x, s_y.
-
variance
(t: float) → float¶ The variance of the model output at a given time instant
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
-
class
vanilla_option_pricing.models.
NumericalModel
(A: numpy.array, B: numpy.array, p_0: numpy.array)¶ Bases:
object
A general-purpose linear stochastic system. All the parameters must be matrices (as Numpy arrays) of suitable dimensions.
Parameters: - A – the dynamic matrix A of the system
- B – the input matrix B of the system
- p_0 – the initial variance, that is the variance of the state at time t=0, must be symmetric and positive semidefinite
-
variance
(t: float) → float¶ The variance of the model output at a given time instant
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
-
class
vanilla_option_pricing.models.
OrnsteinUhlenbeck
(p_0: float, l: float, s: float)¶ Bases:
vanilla_option_pricing.option_pricing.OptionPricingModel
The single-factor, mean-reverting Ornstein-Uhlenbeck process.
Parameters: - p_0 – the initial variance, that is the variance of the state at time t=0, must be positive semidefinite and symmetric
- l – the strength of the mean-reversion, must be non-negative
- s – the volatility, must be non-negative
-
name
= 'Ornstein-Uhlenbeck'¶
-
parameters
¶ Model parameters, as a list of real numbers, in the order [l, s].
-
variance
(t: float) → float¶ The variance of the model output at a given time instant
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
vanilla_option_pricing.option module¶
-
class
vanilla_option_pricing.option.
VanillaOption
(instrument: str, option_type: str, date: datetime.datetime, price: float, strike: float, spot: float, maturity: datetime.datetime, dividend=0)¶ Bases:
object
A European vanilla option. All the prices must share the same currency.
Parameters: - instrument – name of the underlying
- option_type – type of the option (c for call, p for put)
- date – the date when the option is traded
- price – option price
- strike – option strike price
- spot – spot price of the underlying
- maturity – the maturity date
- dividend – underlying dividend - if any, expressed as a decimal number
-
DAYS_IN_YEAR
= 365.2425¶
-
implied_volatility_of_undiscounted_price
¶ The implied volatility of the option, considering an undiscounted price. Returns zero if the implied volatility is negative.
-
to_dict
()¶ Returns: all the fields of the object in a dictionary
-
years_to_maturity
¶ The years remaining to option maturity, as a decimal number.
-
vanilla_option_pricing.option.
check_option_type
(option_type: str)¶ A utility function to check the validity of the type of an option. Raises a ValueError if the type is invalid. :param option_type: the type of the option: valid types are “c” for call and “p” for put
-
vanilla_option_pricing.option.
option_list_to_pandas_dataframe
(options: List[vanilla_option_pricing.option.VanillaOption]) → pandas.core.frame.DataFrame¶ A utility function to convert a list of
VanillaOption
to a pandas dataframe.Parameters: options – a list of VanillaOption
Returns: a pandas dataframe, containing option data
-
vanilla_option_pricing.option.
pandas_dataframe_to_option_list
(data_frame: pandas.core.frame.DataFrame) → List[vanilla_option_pricing.option.VanillaOption]¶ A utility function to convert a pandas dataframe to a list of
VanillaOption
. For this function to work, the dataframe columns should be named as the parameters ofVanillaOption
’s constructor.Parameters: data_frame – a pandas dataframe, containing option data Returns: a list of VanillaOption
vanilla_option_pricing.option_pricing module¶
-
class
vanilla_option_pricing.option_pricing.
OptionPricingModel
¶ Bases:
abc.ABC
A model which can be used to price European vanilla options.
-
parameters
¶ The model parameters, returned as a list of real numbers.
-
price_black
(option_type: str, spot: float, strike: float, years_to_maturity: float) → float¶ Finds the no-arbitrage price of a European Vanilla option. Price is computed using the Black formulae, but the variance of the underlying is extracted from this model.
Parameters: - option_type – the type of the option (c for call, p for put)
- spot – the spot price of the underlying
- strike – the option strike price
- years_to_maturity – the years remaining before maturity - as a decimal number
Returns: the no-arbitrage price of the option
-
price_black_scholes_merton
(option_type: str, spot: float, strike: float, years_to_maturity: float, risk_free_rate: float, dividend: float = 0) → float¶ Finds the no-arbitrage price of a European Vanilla option. The price is computed using the Black-Scholes-Merton framework, but the variance of the underlying is extracted from this model.
Parameters: - option_type – the type of the option (c for call, p for put)
- spot – the spot price of the underlying
- strike – the option strike price
- years_to_maturity – the years remaining before maturity - as a decimal number
- risk_free_rate – the risk-free interest rate
- dividend – the dividend paid by the underlying - as a decimal number
Returns: the no-arbitrage price of the option
-
price_option_black
(option: vanilla_option_pricing.option.VanillaOption) → float¶ Same as
price_black()
, but the details of the vanilla option are provided by aVanillaOption
object.Parameters: option – a VanillaOption
Returns: the no-arbitrage price of the option
-
price_option_black_scholes_merton
(option: vanilla_option_pricing.option.VanillaOption, risk_free_rate: float) → float¶ Same as
price_black_scholes_merton()
, but the details of the vanilla option are provided by aVanillaOption
object.Parameters: - option – a
VanillaOption
- risk_free_rate – the risk-free interest rate
Returns: the no-arbitrage price of the option
- option – a
-
standard_deviation
(t: float) → float¶ The standard deviation of the model output at a given instant, that is the squared root of the
variance()
at the same instantParameters: t – the time when the standard deviation is evaluated Returns: the standard deviation at time t
-
variance
(t: float) → float¶ The variance of the model output at a given time.
Parameters: t – the time when the variance is evaluated Returns: the variance at time t
-
volatility
(t: float) → float¶ The volatility of the model output at a certain instant, that is the
standard_deviation()
divided by the squared root of the timeParameters: t – the time when the volatility is evaluated Returns: the volatility at time t
-