Source code for pfhedge.autogreek

from inspect import signature
from typing import Any
from typing import Callable

import torch
from torch import Tensor

from ._utils.parse import parse_spot
from ._utils.parse import parse_time_to_maturity
from ._utils.parse import parse_volatility


[docs]def delta( pricer: Callable[..., Tensor], *, create_graph: bool = False, **params: Any ) -> Tensor: """Computes and returns delta of a derivative using automatic differentiation. Delta is a differentiation of a derivative price with respect to a price of underlying instrument. Note: The keyword argument ``**params`` should contain at least one of the following combinations: - ``spot`` - ``moneyness`` and ``strike`` - ``log_moneyness`` and ``strike`` Args: pricer (callable): Pricing formula of a derivative. create_graph (bool, default=False): If ``True``, graph of the derivative will be constructed, allowing to compute higher order derivative products. **params: Parameters passed to ``pricer``. Returns: torch.Tensor Examples: Delta of a European option can be evaluated as follows. >>> import pfhedge.autogreek as autogreek >>> from pfhedge.nn import BSEuropeanOption >>> >>> # TODO(simaki): Rewrite using functional >>> pricer = BSEuropeanOption().price >>> autogreek.delta( ... pricer, ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.ones(3), ... volatility=torch.tensor([0.18, 0.20, 0.22]), ... strike=1.0, ... ) tensor([0.5359, 0.5398, 0.5438]) The result matches the analytical solution (as it should). >>> BSEuropeanOption().delta( ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.ones(3), ... volatility=torch.tensor([0.18, 0.20, 0.22]), ... ) tensor([0.5359, 0.5398, 0.5438]) One can evaluate greeks of a price computed by a hedger. >>> from pfhedge.instruments import BrownianStock >>> from pfhedge.instruments import EuropeanOption >>> from pfhedge.nn import WhalleyWilmott >>> from pfhedge.nn import Hedger >>> >>> _ = torch.manual_seed(42) >>> >>> derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(torch.float64) >>> model = WhalleyWilmott(derivative) >>> hedger = Hedger(model, model.inputs()).to(torch.float64) >>> >>> def pricer(spot): ... return hedger.price( ... derivative, init_state=(spot,), enable_grad=True ... ) >>> >>> autogreek.delta(pricer, spot=torch.tensor(1.0)) tensor(0.5...) """ spot = parse_spot(**params).requires_grad_() params["spot"] = spot if "strike" in params: params["moneyness"] = spot / params["strike"] params["log_moneyness"] = (spot / params["strike"]).log() # Delete parameters that are not in the signature of pricer to avoid # TypeError: <pricer> got an unexpected keyword argument '<parameter>' for parameter in list(params.keys()): if parameter not in signature(pricer).parameters.keys(): del params[parameter] price = pricer(**params) return torch.autograd.grad( price, inputs=spot, grad_outputs=torch.ones_like(price), create_graph=create_graph, )[0]
[docs]def gamma( pricer: Callable[..., Tensor], *, create_graph: bool = False, **params: Any ) -> Tensor: """Computes and returns gamma of a derivative. Delta is a second-order differentiation of a derivative price with respect to a price of underlying instrument. Note: The keyword argument ``**params`` should contain at least one of the following combinations: - ``spot`` - ``moneyness`` and ``strike`` - ``log_moneyness`` and ``strike`` Args: pricer (callable): Pricing formula of a derivative. create_graph (bool, default=False): If ``True``, graph of the derivative will be constructed, allowing to compute higher order derivative products. **params: Parameters passed to ``pricer``. Returns: torch.Tensor Examples: Gamma of a European option can be evaluated as follows. >>> import pfhedge.autogreek as autogreek >>> from pfhedge.nn import BSEuropeanOption >>> >>> pricer = BSEuropeanOption().price >>> autogreek.gamma( ... pricer, ... strike=torch.ones(3), ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.ones(3), ... volatility=torch.tensor([0.18, 0.20, 0.22]), ... ) tensor([2.2074, 1.9848, 1.8024]) The result matches the analytical solution (as it should). >>> BSEuropeanOption().gamma( ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.ones(3), ... volatility=torch.tensor([0.18, 0.20, 0.22]), ... ) tensor([2.2074, 1.9848, 1.8024]) """ spot = parse_spot(**params).requires_grad_() params["spot"] = spot if "strike" in params: params["moneyness"] = spot / params["strike"] params["log_moneyness"] = (spot / params["strike"]).log() tensor_delta = delta(pricer, create_graph=True, **params).requires_grad_() return torch.autograd.grad( tensor_delta, inputs=spot, grad_outputs=torch.ones_like(tensor_delta), create_graph=create_graph, )[0]
def gamma_from_delta( fn: Callable[..., Tensor], *, create_graph: bool = False, **params: Any ) -> Tensor: """Computes and returns gamma of a derivative from the formula of delta. Note: The keyword argument ``**params`` should contain at least one of the following combinations: - ``spot`` - ``moneyness`` and ``strike`` - ``log_moneyness`` and ``strike`` Args: fn (callable): Function to calculate delta. create_graph (bool, default=False): If ``True``, graph of the derivative will be constructed, allowing to compute higher order derivative products. **params: Parameters passed to ``fn``. Returns: torch.Tensor """ return delta(pricer=fn, create_graph=create_graph, **params)
[docs]def vega( pricer: Callable[..., Tensor], *, create_graph: bool = False, **params: Any ) -> Tensor: """Computes and returns vega of a derivative using automatic differentiation. Vega is a differentiation of a derivative price with respect to a variance of underlying instrument. Note: The keyword argument ``**params`` should contain at least one of the following parameters: - ``volatility`` - ``variance`` Args: pricer (callable): Pricing formula of a derivative. create_graph (bool, default=False): If ``True``, graph of the derivative will be constructed, allowing to compute higher order derivative products. **params: Parameters passed to ``pricer``. Returns: torch.Tensor Examples: Vega of a European option can be evaluated as follows. >>> import pfhedge.autogreek as autogreek >>> from pfhedge.nn import BSEuropeanOption >>> >>> pricer = BSEuropeanOption().price >>> autogreek.vega( ... pricer, ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.ones(3), ... volatility=torch.tensor([0.18, 0.20, 0.22]), ... ) tensor([0.3973, 0.3970, 0.3965]) """ volatility = parse_volatility(**params).requires_grad_() params["volatility"] = volatility params["variance"] = volatility.square() # Delete parameters that are not in the signature of pricer to avoid # TypeError: <pricer> got an unexpected keyword argument '<parameter>' for parameter in list(params.keys()): if parameter not in signature(pricer).parameters.keys(): del params[parameter] price = pricer(**params) return torch.autograd.grad( price, inputs=volatility, grad_outputs=torch.ones_like(price), create_graph=create_graph, )[0]
[docs]def theta( pricer: Callable[..., Tensor], *, create_graph: bool = False, **params: Any ) -> Tensor: """Computes and returns theta of a derivative using automatic differentiation. Theta is a differentiation of a derivative price with respect to time. Note: The keyword argument ``**params`` should contain at least one of the following parameters: - ``time_to_maturity`` Args: pricer (callable): Pricing formula of a derivative. create_graph (bool, default=False): If ``True``, graph of the derivative will be constructed, allowing to compute higher order derivative products. **params: Parameters passed to ``pricer``. Returns: torch.Tensor Examples: Theta of a European option can be evaluated as follows. >>> import pfhedge.autogreek as autogreek >>> from pfhedge.nn import BSEuropeanOption ... >>> pricer = BSEuropeanOption().price >>> autogreek.theta( ... pricer, ... log_moneyness=torch.zeros(3), ... time_to_maturity=torch.tensor([0.1, 0.2, 0.3]), ... volatility=torch.tensor([0.20, 0.20, 0.20]), ... ) tensor([-0.1261, -0.0891, -0.0727]) """ time_to_maturity = parse_time_to_maturity(**params).requires_grad_() params["time_to_maturity"] = time_to_maturity # Delete parameters that are not in the signature of pricer to avoid # TypeError: <pricer> got an unexpected keyword argument '<parameter>' for parameter in list(params.keys()): if parameter not in signature(pricer).parameters.keys(): del params[parameter] price = pricer(**params) # Note: usually theta is calculated reversely (\partial{S}/\partial{T} = \partial{S}/\partial{-time_to_maturity}) return -torch.autograd.grad( price, inputs=time_to_maturity, grad_outputs=torch.ones_like(price), create_graph=create_graph, )[0]