# -*- coding: utf-8 -*-
# dcf
# ---
# A Python library for generating discounted cashflows.
#
# Author: sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version: 0.7, copyright Sunday, 22 May 2022
# Website: https://github.com/sonntagsgesicht/dcf
# License: Apache License 2.0 (see LICENSE file)
from math import exp, log, sqrt
from prettyclass import prettyclass
r""" The pre-factor of the normal density: $1/\sqrt{2\Pi}$. """
_ONE_OVER_SQRT_OF_TWO_PI = 0.398942280401433
def _normal_pdf(x):
""" Density function for normal distribution
@param x: float value
@return value of normal density function
"""
return _ONE_OVER_SQRT_OF_TWO_PI * exp(-0.5 * x * x)
def _normal_cdf(x):
"""
The cumulative distribution function of the standard normal distribution.
The standard implementation, following Abramowitz/Stegun, (26.2.17).
"""
if x >= 0:
# if x > 7.0:
# return 1 - _ONE_OVER_SQRT_OF_TWO_PI * exp(-0.5*x*x)/sqrt(1.0+x*x)
result = 1.0 / (1.0 + 0.2316419 * x)
ret = 1.0 - _ONE_OVER_SQRT_OF_TWO_PI * exp(-0.5 * x * x) * (
result * (0.31938153 +
result * (-0.356563782 +
result * (1.781477937 +
result * (-1.821255978 +
result * 1.330274429)))))
return ret
else:
# if x < -7.0:
# return 1 - one_over_sqrt_of_two_pi() * exp(-0.5*x*x)/sqrt(1.0+x*x)
result = 1.0 / (1.0 - 0.2316419 * x)
ret = _ONE_OVER_SQRT_OF_TWO_PI * exp(-0.5 * x * x) * (
result * (0.31938153 +
result * (-0.356563782 +
result * (1.781477937 +
result * (-1.821255978 +
result * 1.330274429)))))
return ret
[docs]
class Intrinsic(OptionPricingFormula):
r""" intrisic option pricing formula
implemented for call options
(`see more on intrisic option values
<https://en.wikipedia.org/wiki/Option_time_value>`_)
Let $F$ be the current forward value.
Let $K$ be the option strike value,
$\tau$ the time to matruity, i.e. the option expitry date.
Then
* call price: $$\max(F-K, 0)$$
* call delta: $$0 \text{ if } F < K \text{ else } 1$$
* call gamma: $$0$$
* call vega: $$0$$
* binary call price: $$0 \text{ if } F < K \text{ else } 1$$
* binary call delta: $$0$$
* binary call gamma: $$0$$
* binary call vega: $$0$$
"""
# --- vanilla
def __call__(self, time, strike, forward, volatility):
return max(forward - strike, 0.0)
[docs]
def delta(self, time, strike, forward, volatility):
return 0.0 if forward < strike else 1.0
[docs]
def gamma(self, time, strike, forward, volatility):
return 0.0
[docs]
def vega(self, time, strike, forward, volatility):
return 0.0
[docs]
def theta(self, time, strike, forward, volatility):
return 0.0
# --- binary
[docs]
def binary(self, time, strike, forward, volatility):
return 0.0 if forward <= strike else 1.0
[docs]
def binary_delta(self, time, strike, forward, volatility):
return 0.0
[docs]
def binary_gamma(self, time, strike, forward, volatility):
return 0.0
[docs]
def binary_vega(self, time, strike, forward, volatility):
return 0.0
[docs]
def binary_theta(self, time, strike, forward, volatility):
return 0.0
[docs]
class Bachelier(OptionPricingFormula):
r""" Bachelier option pricing formula
implemented for call options
(`see more on Bacheliers model
<https://en.wikipedia.org/wiki/Bachelier_model>`_)
Let $f$ be a normaly distributed random variable
with expectation $F=E[f]$, the current forward value
and $\Phi$ the standard normal cummulative distribution function
s.th. $\phi=\Phi'$ is its density function.
Let $K$ be the option strike value,
$\tau$ the time to matruity, i.e. the option expitry date, and
$\sigma$ the volatility parameter,
i.e. the standard deviation of $f$.
Moreover, let $$d = \frac{F-K}{\sigma \cdot \sqrt{\tau}}$$
Then
* call price:
$$(F-K) \cdot \Phi(d) + \sigma \cdot \sqrt{\tau} \cdot \phi(d)$$
* call delta: $$\Phi(d)$$
* call gamma: $$\frac{\phi(d)}{\sigma \cdot \sqrt{\tau}}$$
* call vega: $$\sqrt{\tau} \cdot \phi(d)$$
* binary call price: $$\Phi(d)$$
* binary call delta: $$\frac{\phi(d)}{\sigma \cdot \sqrt{\tau}}$$
* binary call gamma: $$d \cdot \frac{\phi(d)}{\sigma^2 \cdot \tau}$$
* binary call vega: $$\sqrt{\tau} \cdot \phi(d)$$
"""
# --- vanilla
def __call__(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return (forward - strike) * _normal_cdf(d) + vol * _normal_pdf(d)
[docs]
def delta(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return _normal_cdf(d)
[docs]
def gamma(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return _normal_pdf(d) / vol
[docs]
def vega(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return sqrt(time) * _normal_pdf(d)
# --- binary
[docs]
def binary(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return _normal_cdf(d)
[docs]
def binary_delta(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return _normal_pdf(d) / vol
[docs]
def binary_gamma(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return d * _normal_pdf(d) / (vol * vol * time)
[docs]
def binary_vega(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (forward - strike) / vol
return sqrt(time) * _normal_pdf(d)
[docs]
class Black76(OptionPricingFormula):
r""" Black 76 option pricing formula
implemented for call options
(`see more on Black 76 model
<https://en.wikipedia.org/wiki/Black_model>`_
which is closly related to the
`Black-Scholes model
<https://en.wikipedia.org/wiki/Black–Scholes_model>`_)
Let $f$ be a log-normaly distributed random variable
with expectation $F=E[f]$, the current forward value
and $\Phi$ the standard normal cummulative distribution function
s.th. $\phi=\Phi'$ is its density function.
Let $K$ be the option strike value,
$\tau$ the time to maturity, i.e. the option expiry date, and
$\sigma$ the volatility parameter,
i.e. the standard deviation of $\log(f)$.
Moreover, let
$$d =\frac{\log(F/K) + (\sigma^2 \cdot \tau)/2}{\sigma \cdot \sqrt{\tau}}$$
Then
* call price:
$$F \cdot \Phi(d) - K \cdot \Phi(d-\sigma \cdot \sqrt{\tau})$$
* call delta: $$\Phi(d)$$
* call gamma: $$\frac{\phi(d)}{F \cdot \sigma \cdot \sqrt{\tau}}$$
* call vega: $$F \cdot \sqrt{\tau} \cdot \phi(d)$$
* binary call price: $$\Phi(d)$$
* binary call delta:
$$\frac{\phi(d-\sigma \cdot \sqrt{\tau})}{\sigma \cdot \sqrt{\tau}}$$
* binary call gamma: $$d \cdot \frac{\phi(d)}{\sigma^2 \cdot \tau}$$
* binary call vega: $$(d/\sigma - \sqrt{\tau}) \cdot \phi(d)$$
"""
# --- vanilla
def __call__(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return forward * _normal_cdf(d) - strike * _normal_cdf(d - vol)
[docs]
def delta(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return _normal_cdf(d)
[docs]
def gamma(self, time, strike, forward, volatility):
# vol = volatility * sqrt(time)
# d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return # TODO Black76 gamma
[docs]
def vega(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return forward * sqrt(time) * _normal_pdf(d - vol)
# --- binary
[docs]
def binary(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return _normal_cdf(d)
[docs]
def binary_delta(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return _normal_pdf(d - vol) / (vol * forward)
[docs]
def binary_gamma(self, time, strike, forward, volatility):
# vol = volatility * sqrt(time)
# d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return # TODO Black76 gamma for digital payoff
[docs]
def binary_vega(self, time, strike, forward, volatility):
vol = volatility * sqrt(time)
d = (log(forward / strike) + (vol ** 2 / 2)) / vol
return (d - vol) * _normal_pdf(d) / volatility
[docs]
class DisplacedBlack76(OptionPricingFormula):
r""" displaced Black 76 option pricing formula
implemented for call options
(see also |Black76()|)
The displaced Black 76 is adopted to handel moderate negative
underlying forward prices or rates $f$,
e.g. as see for interest rates in the past.
To do so, rather than $f$ a shifted or displaced version $f + \alpha$
is assumed to be log-normally distributed
for some negative value of $\alpha$.
Hence, let $f + \alpha$ be a log-normally distributed random variable
with expectation $F + \alpha=E[f + \alpha]$,
where $F=E[f]$ is the current forward value
and $\Phi$ the standard normal cumulative distribution function
s.th. $\phi=\Phi'$ is its density function.
Uses |Black76()| formulas
with
* displaced forward $F+\alpha$
and
* displaced strike $K+\alpha$
Let $K$ be the option strike value,
$\tau$ the time to maturity, i.e. the option expiry date, and
$\sigma$ the volatility parameter,
i.e. the standard deviation of $\log(f + \alpha)$.
Moreover, let
$$d =\frac{\log((F+\alpha)/(K+\alpha))
+ (\sigma^2 \cdot \tau)/2}{\sigma \cdot \sqrt{\tau}}$$
Then
* call price:
$$(F+\alpha) \cdot \Phi(d)
- (K+\alpha) \cdot \Phi(d-\sigma \cdot \sqrt{\tau})$$
* call delta: $$\Phi(d)$$
* call gamma:
$$\frac{\phi(d)}{(F+\alpha) \cdot \sigma \cdot \sqrt{\tau}}$$
* call vega: $$(F+\alpha) \cdot \sqrt{\tau} \cdot \phi(d)$$
* binary call price:
* binary call delta:
* binary call gamma:
* binary call vega:
"""
INNER = Black76()
def __init__(self, displacement=0.0):
self.displacement = displacement
def __call__(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER(time, strike, forward, volatility)
[docs]
def delta(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.delta(time, strike, forward, volatility)
[docs]
def gamma(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.gamma(time, strike, forward, volatility)
[docs]
def vega(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.vega(time, strike, forward, volatility)
[docs]
def theta(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.theta(time, strike, forward, volatility)
# --- binary
[docs]
def binary(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.binary(time, strike, forward, volatility)
[docs]
def binary_delta(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.binary_delta(time, strike, forward, volatility)
[docs]
def binary_gamma(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.binary_gamma(time, strike, forward, volatility)
[docs]
def binary_vega(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.binary_vega(time, strike, forward, volatility)
[docs]
def binary_theta(self, time, strike, forward, volatility):
strike += self.displacement
forward += self.displacement
return self.INNER.binary_theta(time, strike, forward, volatility)