Skip to content

Commit

Permalink
Release/0.16.0 (#482)
Browse files Browse the repository at this point in the history
* ENH: Add SVI model: `svi_variance` and `SVIVariance` (#406) (#410)

* ENH: Add Sobol quasirandom engine (#430) (#431) (#478)

* ENH: Support `BSEuropeanBinary` for put (#434) (#438)

* ENH: Enabling customizing tqdm progress bar (#446)

* ENH: Add antithetic sampling of randn (close #449) (#450)

* DOC: Add an example of hedging variance swap using options (#426) (#435)

* DOC: Add `autogreek.theta` to documentation (#429)

* DOC: Add citation to Heston model (#447)

* DOC: Add an example of sticky strike and sticky delta (#479)

* TEST: Add tests for BSEuropean put (#439)

* TEST: Add tests for identity between vega and gamma (#441)

* MAINT: Update codecov action (#442)
  • Loading branch information
simaki authored Feb 8, 2022
1 parent bc4ae30 commit c342b52
Show file tree
Hide file tree
Showing 33 changed files with 741 additions and 259 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pytest:

.PHONY: test-cov
test-cov:
$(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=html
$(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=xml

.PHONY: lint
lint: lint-black lint-isort
Expand Down
5 changes: 5 additions & 0 deletions docs/source/autogreek.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ Vega
----

.. autofunction:: vega

Theta
-----

.. autofunction:: theta
1 change: 1 addition & 0 deletions docs/source/nn.functional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ Other Functions
npdf
d1
d2
svi_variance
1 change: 1 addition & 0 deletions docs/source/nn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,4 @@ Other Modules
:template: classtemplate.rst

nn.Naked
nn.SVIVariance
10 changes: 10 additions & 0 deletions docs/source/stochastic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@ Heston Process
:toctree: generated

generate_heston

Generators
----------

.. autosummary::
:nosignatures:
:toctree: generated

randn_antithetic
randn_sobol_boxmuller
35 changes: 35 additions & 0 deletions examples/example_hedging_variance_swap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sys

import matplotlib.pyplot as plt
import torch

sys.path.append("..")

from pfhedge.instruments import BrownianStock
from pfhedge.instruments import EuropeanOption
from pfhedge.nn import BlackScholes

if __name__ == "__main__":
options_list = []
strikes_list = []
for call in (True, False):
for strike in torch.arange(70, 180, 10):
option = EuropeanOption(BrownianStock(), call=call, strike=strike)
options_list.append(option)
strikes_list.append(strike)

spot = torch.linspace(50, 200, 100)
t = options_list[0].maturity
v = options_list[0].ul().sigma

plt.figure()
total_vega = torch.zeros_like(spot)
for option, strike in zip(options_list, strikes_list):
lm = (spot / strike).log()
vega = BlackScholes(option).vega(lm, t, v) / (strike**2)
total_vega += vega
if option.call:
# 2 is for call and put
plt.plot(spot.numpy(), 2 * vega.numpy())
plt.plot(spot.numpy(), total_vega.numpy(), color="k", lw=2)
plt.savefig("./output/options-vega.png")
45 changes: 45 additions & 0 deletions examples/example_svi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys

import matplotlib.pyplot as plt
import torch

sys.path.append("..")

import pfhedge.autogreek as autogreek
from pfhedge.nn import BSEuropeanOption
from pfhedge.nn.functional import svi_variance

if __name__ == "__main__":
a, b, rho, m, sigma = 0.02, 0.10, -0.40, 0.00, 0.20
k = torch.linspace(-0.10, 0.10, 100)
v = svi_variance(k, a=a, b=b, rho=rho, m=m, sigma=sigma)

plt.figure()
plt.plot(k.numpy(), v.numpy())
plt.xlabel("Log strike")
plt.xlabel("Variance")
plt.savefig("output/svi_variance.pdf")
print("Saved figure to output/svi_variance.pdf")

bs = BSEuropeanOption()
t = torch.full_like(k, 0.1)
delta_sticky_strike = bs.delta(-k, t, v.sqrt())

def price_sticky_delta(log_moneyness):
v = svi_variance(-log_moneyness, a=a, b=b, rho=rho, m=m, sigma=sigma)
return bs.price(log_moneyness, t, v.sqrt())

log_moneyness = -k.requires_grad_()
delta_sticky_delta = autogreek.delta(
price_sticky_delta, log_moneyness=log_moneyness
)
k.detach_()

plt.figure()
plt.plot(-k.numpy(), delta_sticky_strike.numpy(), label="Delta for sticky strike")
plt.plot(-k.numpy(), delta_sticky_delta.numpy(), label="Delta for sticky delta")
plt.xlabel("Log strike")
plt.xlabel("Delta")
plt.legend()
plt.savefig("output/svi_delta.pdf")
print("Saved figure to output/svi_delta.pdf")
2 changes: 1 addition & 1 deletion pfhedge/instruments/primary/brownian.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def variance(self) -> Tensor:
It is a tensor filled with the square of ``self.sigma``.
"""
return torch.full_like(self.spot, self.sigma ** 2)
return torch.full_like(self.spot, self.sigma**2)

def simulate(
self,
Expand Down
1 change: 1 addition & 0 deletions pfhedge/nn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
from .modules.loss import IsoelasticLoss
from .modules.mlp import MultiLayerPerceptron
from .modules.naked import Naked
from .modules.svi import SVIVariance
from .modules.ww import WhalleyWilmott
28 changes: 28 additions & 0 deletions pfhedge/nn/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,3 +565,31 @@ def ww_width(
torch.Tensor
"""
return (cost * (3 / 2) * gamma.square() * spot / a).pow(1 / 3)


def svi_variance(
input: TensorOrScalar,
a: TensorOrScalar,
b: TensorOrScalar,
rho: TensorOrScalar,
m: TensorOrScalar,
sigma: TensorOrScalar,
) -> Tensor:
r"""Returns variance in the SVI model.
See :class:`pfhedge.nn.SVIVariance` for details.
Args:
input (torch.Tensor or float): Log strike of the underlying asset.
That is, :math:`k = \log(K / S)` for spot :math:`S` and strike :math:`K`.
a (torch.Tensor or float): The parameter :math:`a`.
b (torch.Tensor or float): The parameter :math:`b`.
rho (torch.Tensor or float): The parameter :math:`\rho`.
m (torch.Tensor or float): The parameter :math:`m`.
sigma (torch.Tensor or float): The parameter :math:`s`.
Returns:
torch.Tensor
"""
k_m = torch.as_tensor(input - m) # k - m
return a + b * (rho * k_m + (k_m.square() + sigma**2).sqrt())
2 changes: 0 additions & 2 deletions pfhedge/nn/modules/bs/american_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ class BSAmericanBinaryOption(BSModuleMixin):
Continuous-time models (Vol. 11). Springer Science & Business Media.
Examples:
>>> from pfhedge.nn import BSAmericanBinaryOption
>>>
>>> m = BSAmericanBinaryOption(strike=1.0)
Expand Down Expand Up @@ -81,7 +80,6 @@ def from_derivative(cls, derivative):
BSAmericanBinaryOption
Examples:
>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import AmericanBinaryOption
>>>
Expand Down
1 change: 0 additions & 1 deletion pfhedge/nn/modules/bs/european.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def from_derivative(cls, derivative):
BSEuropeanOption
Examples:
>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>>
Expand Down
9 changes: 4 additions & 5 deletions pfhedge/nn/modules/bs/european_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ class BSEuropeanBinaryOption(BSModuleMixin):
"""

def __init__(self, call: bool = True, strike: float = 1.0):
if not call:
raise ValueError(
f"{self.__class__.__name__} for a put option is not yet supported."
)

super().__init__()
self.call = call
self.strike = strike
Expand Down Expand Up @@ -92,6 +87,8 @@ def from_derivative(cls, derivative):

def extra_repr(self) -> str:
params = []
if not self.call:
params.append("call=" + str(self.call))
params.append("strike=" + _format_float(self.strike))
return ", ".join(params)

Expand Down Expand Up @@ -148,6 +145,8 @@ def delta(

spot = s.exp() * self.strike
delta = npdf(d2(s, t, v)) / (spot * v * t.sqrt())
delta = -delta if not self.call else delta # put-call parity

return delta

def gamma(
Expand Down
5 changes: 4 additions & 1 deletion pfhedge/nn/modules/hedger.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ def fit(
init_state: Optional[Tuple[TensorOrScalar, ...]] = None,
verbose: bool = True,
validation: bool = True,
tqdm_kwargs: dict = {},
) -> Optional[List[float]]:
"""Fit the hedging model to hedge a given derivative.
Expand Down Expand Up @@ -489,6 +490,8 @@ def fit(
standard output.
validation (bool, default=True): If ``False``, skip the computation of the
validation loss and returns ``None``.
tqdm_kwargs (dict, default={}): Keyword argument passed to ``tqdm.__init__``
to customize the progress bar.
Returns:
list[float]
Expand Down Expand Up @@ -546,7 +549,7 @@ def compute_loss(**kwargs) -> Tensor:
)

history = []
progress = tqdm(range(n_epochs), disable=not verbose)
progress = tqdm(range(n_epochs), disable=not verbose, **tqdm_kwargs)
for _ in progress:
# Compute training loss and backpropagate
self.train()
Expand Down
57 changes: 57 additions & 0 deletions pfhedge/nn/modules/svi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from torch import Tensor
from torch.nn import Module

from pfhedge._utils.typing import TensorOrScalar
from pfhedge.nn.functional import svi_variance


class SVIVariance(Module):
r"""Returns total variance in the SVI model.
The total variance for log strike :math:`k = \log(K / S)`,
where :math:`K` and :math:`S` are strike and spot, reads:
.. math::
w = a + b \left[ \rho (k - m) + \sqrt{(k - m)^2 + \sigma^2} \right] .
References:
- Jim Gatheral and Antoine Jacquier,
Arbitrage-free SVI volatility surfaces.
[arXiv:`1204.0646 <https://arxiv.org/abs/1204.0646>`_ [q-fin.PR]]
Args:
a (torch.Tensor or float): The parameter :math:`a`.
b (torch.Tensor or float): The parameter :math:`b`.
rho (torch.Tensor or float): The parameter :math:`\rho`.
m (torch.Tensor or float): The parameter :math:`m`.
sigma (torch.Tensor or float): The parameter :math:`\sigma`.
Examples:
>>> import torch
>>>
>>> a, b, rho, m, sigma = 0.03, 0.10, 0.10, 0.00, 0.10
>>> module = SVIVariance(a, b, rho, m, sigma)
>>> input = torch.tensor([-0.10, -0.01, 0.00, 0.01, 0.10])
>>> module(input)
tensor([0.0431, 0.0399, 0.0400, 0.0401, 0.0451])
"""

def __init__(
self,
a: TensorOrScalar,
b: TensorOrScalar,
rho: TensorOrScalar,
m: TensorOrScalar,
sigma: TensorOrScalar,
) -> None:
super().__init__()
self.a = a
self.b = b
self.rho = rho
self.m = m
self.sigma = sigma

def forward(self, input: Tensor) -> Tensor:
return svi_variance(
input, a=self.a, b=self.b, rho=self.rho, m=self.m, sigma=self.sigma
)
2 changes: 2 additions & 0 deletions pfhedge/stochastic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
from .brownian import generate_geometric_brownian
from .cir import generate_cir
from .heston import generate_heston
from .random import randn_antithetic
from .random import randn_sobol_boxmuller
Loading

0 comments on commit c342b52

Please sign in to comment.