Skip to content

Commit

Permalink
Greeks for binary options
Browse files Browse the repository at this point in the history
  • Loading branch information
CarloLepelaars committed Nov 6, 2023
1 parent 6fc6830 commit fe899c0
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 41 deletions.
53 changes: 53 additions & 0 deletions blackscholes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,4 +627,57 @@ def _d1(self) -> float:
def _d2(self) -> float:
"""2nd probability parameter that acts as a multiplication factor for discounting."""
return self._d1 - self.sigma * sqrt(self.T)

@abstractmethod
def delta(self) -> float:
"""Rate of change in structure price
with respect to the asset price (1st derivative).
Note that this is the forward delta.
"""
...

def gamma(self) -> float:
"""Rate of change in delta
with respect to the underlying price (2nd derivative).
"""
return (self._pdf(self._d1) * (self._d1 / (self.T * self.sigma * self.S) - 1 / self.S**2)) / (self.S * self.sigma * sqrt(self.T))

@abstractmethod
def vega(self) -> float:
"""Rate of change in option price
with respect to the volatility (1st derivative).
"""
...

@abstractmethod
def theta(self) -> float:
"""
Rate of change in structure price
with respect to time (i.e. time decay).
"""
...

@abstractmethod
def rho(self) -> float:
"""Rate of change in structure price
with respect to the risk-free rate.
"""
...

def get_core_greeks(self) -> Dict[str, float]:
"""
Get the top 5 most well known Greeks for the binary option.
1. Delta
2. Gamma
3. Vega
4. Theta
5. Rho
"""
return {
"delta": self.delta(),
"gamma": self.gamma(),
"vega": self.vega(),
"theta": self.theta(),
"rho": self.rho(),
}

27 changes: 26 additions & 1 deletion blackscholes/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,29 @@ def price(self) -> float:
def forward(self) -> float:
"""Fair value of binary call option without discounting for interest rates."""
return self._cdf(self._d2)


def delta(self) -> float:
"""Rate of change in option price
with respect to the forward price (1st derivative).
Note that this is the forward delta.
"""
return exp(-self.r * self.T) * self._pdf(self._d1) / sqrt(self.T)

def vega(self) -> float:
"""Rate of change in option price
with respect to the volatility (1st derivative).
"""
return self.S * sqrt(self.T) * self._pdf(self._d1) * self._d1 / self.sigma

def theta(self) -> float:
"""Rate of change in option price
with respect to time (i.e. time decay).
"""
return self.r * self.K * exp(-self.r * self.T) * self._cdf(self._d2) - (self.S * self._pdf(self._d1) * self.sigma) / (2 * sqrt(self.T))

def rho(self) -> float:
"""Rate of change in option price
with respect to the risk-free rate.
"""
return self.T * self.K * exp(-self.r * self.T) * self._cdf(self._d2)

24 changes: 24 additions & 0 deletions blackscholes/put.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,28 @@ def price(self) -> float:
def forward(self) -> float:
"""Fair value of binary call option without discounting for interest rates."""
return 1 - self._cdf(self._d2)

def delta(self) -> float:
"""Rate of change in option price
with respect to the underlying price (1st derivative).
"""
return -exp(-self.r * self.T) * self._pdf(self._d1) / sqrt(self.T)

def vega(self) -> float:
"""Rate of change in option price
with respect to the volatility (1st derivative).
"""
return -self.S * sqrt(self.T) * self._pdf(self._d1) * self._d1 / self.sigma

def theta(self) -> float:
"""Rate of change in option price
with respect to time (i.e. time decay).
"""
return -self.r * self.K * exp(-self.r * self.T) * self._cdf(-self._d2) - (self.S * self._pdf(self._d1) * self.sigma) / (2 * sqrt(self.T))

def rho(self) -> float:
"""Rate of change in option price
with respect to the risk-free rate.
"""
return -(self.T * self.K * exp(-self.r * self.T) * self._cdf(-self._d2))

81 changes: 80 additions & 1 deletion blackscholes/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest
from scipy.stats import norm

from blackscholes.base import Black76Base, BlackScholesBase, StandardNormalMixin
from blackscholes.base import Black76Base, BlackScholesBase, BinaryBase, StandardNormalMixin

# Test parameters
test_S = 55.0 # Asset price of 55
Expand Down Expand Up @@ -233,3 +233,82 @@ def test_vanna(self):
def test_vomma(self):
vomma = self.meta.vomma()
np.testing.assert_almost_equal(vomma, 45.13472833935059, decimal=5)


class BinaryMeta(BinaryBase):
"""Dummy class for testing Binary base methods."""

def __init__(self, S: float, K: float, T: float, r: float, sigma: float):
super().__init__(S=S, K=K, T=T, r=r, sigma=sigma)

def price(self):
...

def forward(self):
...

def delta(self):
...

def vega(self):
...

def theta(self):
...

def rho(self):
...


class TestBinaryBase:
meta = BinaryMeta(S=test_S, K=test_K, T=test_T, r=test_r, sigma=test_sigma)

def test_arg_assert(self):
# Should not be able to initialize if S, K, T, or sigma is negative.
with pytest.raises(AssertionError):
BinaryMeta(
S=-test_S,
K=test_K,
T=test_T,
r=test_r,
sigma=test_sigma,
)
BinaryMeta(
S=test_S,
K=-test_K,
T=test_T,
r=test_r,
sigma=test_sigma,
)
BinaryMeta(
S=-test_S,
K=test_K,
T=-test_T,
r=test_r,
sigma=test_sigma,
)
BinaryMeta(
S=test_S,
K=test_K,
T=test_T,
r=test_r,
sigma=-test_sigma,
)

# Initializing with negative r (interest rate) is possible.
BinaryMeta(
S=test_S,
K=test_K,
T=test_T,
r=-test_r,
sigma=test_sigma,
)

def test_d(self):
# d1 and d2 should be accurate up to at least 6 decimals
np.testing.assert_almost_equal(self.meta._d1, 0.7270678653621663, decimal=6)
np.testing.assert_almost_equal(self.meta._d2, 0.5770678653621663, decimal=6)

def test_gamma(self):
gamma = self.meta.gamma()
np.testing.assert_almost_equal(gamma, 0.0032595297589864043, decimal=6)
42 changes: 42 additions & 0 deletions blackscholes/tests/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def test_all_greeks(self):

class TestBinaryCall:
call = BinaryCall(S=test_S, K=test_K, T=test_T, r=test_r, sigma=test_sigma)
put = BinaryPut(S=test_S, K=test_K, T=test_T, r=test_r, sigma=test_sigma)

def test_price(self):
price = self.call.price()
Expand All @@ -229,3 +230,44 @@ def test_price(self):
def test_forward(self):
forward = self.call.forward()
np.testing.assert_almost_equal(forward, 0.7180531943767934, decimal=6)

def test_delta(self):
delta = self.call.delta()
np.testing.assert_almost_equal(delta, 0.3055162306516324, decimal=6)

def test_gamma(self):
gamma = self.call.gamma()
put_gamma = self.put.gamma()
np.testing.assert_almost_equal(gamma, put_gamma, decimal=15)
np.testing.assert_almost_equal(gamma, 0.0032595297589864043, decimal=6)

def test_vega(self):
vega = self.call.vega()
put_vega = self.put.vega()
np.testing.assert_almost_equal(vega, -put_vega, decimal=15)
np.testing.assert_almost_equal(vega, 81.65192052446703, decimal=6)

def test_theta(self):
theta = self.call.theta()
np.testing.assert_almost_equal(theta, -1.1738764912159139, decimal=6)

def test_rho(self):
rho = self.call.rho()
np.testing.assert_almost_equal(rho, 35.813015171916085, decimal=6)

def test_get_core_greeks(self):
core_greeks = self.call.get_core_greeks()
expected_result = {
"delta": 0.3055162306516324,
"gamma": 0.0032595297589864043,
"vega": 81.65192052446703,
"theta": -1.1738764912159139,
"rho": 35.813015171916085,
}

assert set(core_greeks.keys()) == set(expected_result.keys())
for key in expected_result.keys():
np.testing.assert_almost_equal(
core_greeks[key], expected_result[key], decimal=5
)

43 changes: 42 additions & 1 deletion blackscholes/tests/test_put.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np

from blackscholes import Black76Call, Black76Put, BlackScholesCall, BlackScholesPut, BinaryPut
from blackscholes import Black76Call, Black76Put, BlackScholesCall, BlackScholesPut, BinaryPut, BinaryCall

# Test parameters
test_S = 55.0 # Asset price of 55
Expand Down Expand Up @@ -206,6 +206,7 @@ def test_all_greeks(self):

class TestBinaryPut:
put = BinaryPut(S=test_S, K=test_K, T=test_T, r=test_r, sigma=test_sigma)
call = BinaryCall(S=test_S, K=test_K, T=test_T, r=test_r, sigma=test_sigma)

def test_price(self):
price = self.put.price()
Expand All @@ -214,4 +215,44 @@ def test_price(self):
def test_forward(self):
forward = self.put.forward()
np.testing.assert_almost_equal(forward, 0.2819468056232066, decimal=6)

def test_delta(self):
delta = self.put.delta()
np.testing.assert_almost_equal(delta, -0.3055162306516324, decimal=6)

def test_gamma(self):
gamma = self.put.gamma()
call_gamma = self.call.gamma()
np.testing.assert_almost_equal(gamma, call_gamma, decimal=16)
np.testing.assert_almost_equal(gamma, 0.0032595297589864043, decimal=6)

def test_vega(self):
vega = self.put.vega()
call_vega = self.call.vega()
np.testing.assert_almost_equal(vega, -call_vega, decimal=16)
np.testing.assert_almost_equal(vega, -81.65192052446703, decimal=6)

def test_theta(self):
theta = self.put.theta()
np.testing.assert_almost_equal(theta, -1.2985643815155963, decimal=6)

def test_rho(self):
rho = self.put.rho()
np.testing.assert_almost_equal(rho, -14.062140947956921, decimal=6)

def test_get_core_greeks(self):
core_greeks = self.put.get_core_greeks()
expected_result = {
"delta": -0.3055162306516324,
"gamma": 0.0032595297589864043,
"vega": -81.65192052446703,
"theta": -1.2985643815155963,
"rho": -14.062140947956921,
}

assert set(core_greeks.keys()) == set(expected_result.keys())
for key in expected_result.keys():
np.testing.assert_almost_equal(
core_greeks[key], expected_result[key], decimal=5
)

2 changes: 1 addition & 1 deletion docs/2.price.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ put.price() ## 1.2470

## Binary options

`blackscholes` supports calculation of the price and the forward (undiscounted price) of binary options. Also called a digital or exotic option.
`blackscholes` supports calculation of the price and the forward (undiscounted price) of binary options. Also called a digital, exotic or bet option.

### Call

Expand Down
Loading

0 comments on commit fe899c0

Please sign in to comment.