Source code for pfhedge.nn.modules.bs.lookback

from typing import List
from typing import Optional

import torch
from torch import Tensor

from pfhedge._utils.bisect import find_implied_volatility
from pfhedge._utils.doc import _set_attr_and_docstring
from pfhedge._utils.doc import _set_docstring
from pfhedge._utils.str import _format_float
from pfhedge.instruments import LookbackOption
from pfhedge.nn.functional import bs_lookback_price

from ._base import BSModuleMixin
from ._base import acquire_params_from_derivative_0
from ._base import acquire_params_from_derivative_2
from .black_scholes import BlackScholesModuleFactory


[docs]class BSLookbackOption(BSModuleMixin): """Black-Scholes formula for a lookback option with a fixed strike. Note: - The formulas are for continuous monitoring while :class:`pfhedge.instruments.LookbackOption` monitors spot prices discretely. To get adjustment for discrete monitoring, see, for instance, Broadie, Glasserman, and Kou (1999). .. seealso:: - :class:`pfhedge.nn.BlackScholes`: Initialize Black-Scholes formula module from a derivative. - :class:`pfhedge.instruments.LookbackOption`: Corresponding derivative. References: - Conze, A., 1991. Path dependent options: The case of lookback options. The Journal of Finance, 46(5), pp.1893-1907. - Broadie, M., Glasserman, P. and Kou, S.G., 1999. Connecting discrete and continuous path-dependent options. Finance and Stochastics, 3(1), pp.55-82. Args: call (bool, default=True): Specifies whether the option is call or put. strike (float, default=1.0): The strike price of the option. Shape: - Input: :math:`(N, *, 4)` where :math:`*` means any number of additional dimensions. See :meth:`inputs` for the names of input features. - Output: :math:`(N, *, 1)`. All but the last dimension are the same shape as the input. Examples: >>> from pfhedge.nn import BSLookbackOption >>> >>> m = BSLookbackOption() >>> m.inputs() ['log_moneyness', 'max_log_moneyness', 'time_to_maturity', 'volatility'] >>> input = torch.tensor([ ... [-0.01, -0.01, 0.1, 0.2], ... [ 0.00, 0.00, 0.1, 0.2], ... [ 0.01, 0.01, 0.1, 0.2]]) >>> m(input) tensor([[0.9208], [1.0515], [1.0515]]) """ def __init__( self, call: bool = True, strike: float = 1.0, derivative: Optional[LookbackOption] = None, ) -> None: 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 self.derivative = derivative
[docs] @classmethod def from_derivative(cls, derivative): """Initialize a module from a derivative. Args: derivative (:class:`pfhedge.instruments.LookbackOption`): The derivative to get the Black-Scholes formula. Returns: BSLookbackOption Examples: >>> from pfhedge.instruments import BrownianStock >>> from pfhedge.instruments import LookbackOption >>> >>> derivative = LookbackOption(BrownianStock(), strike=1.1) >>> m = BSLookbackOption.from_derivative(derivative) >>> m BSLookbackOption(strike=1.1000) """ return cls( call=derivative.call, strike=derivative.strike, derivative=derivative )
def extra_repr(self) -> str: params = [] params.append("strike=" + _format_float(self.strike)) return ", ".join(params)
[docs] def inputs(self) -> List[str]: return ["log_moneyness", "max_log_moneyness", "time_to_maturity", "volatility"]
[docs] def price( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, volatility: Optional[Tensor] = None, ) -> Tensor: r"""Returns price of the derivative. The price is given by: .. math:: \begin{cases} S(0) \{ N(d_1) + \sigma \sqrt{T} [N'(d_1) + d_1 N(d_1)] \} - K N(d_2) & (M \leq K) \\ S(0) \{ N(d_1') + \sigma \sqrt{T} [N'(d_1') + d_1' N(d_1')] \} - K + M [1 - N(d_2')] & (M > K) \\ \end{cases} where :math:`M = \max_{t < 0} S(t)`, :math:`d_1' = [\log(S(0) / M) + \frac12 \sigma^2 T] / \sigma \sqrt{T}`, and :math:`d_2' = [\log(S(0) / M) - \frac12 \sigma^2 T] / \sigma \sqrt{T}`. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. volatility (torch.Tensor, optional): Volatility of the underlying asset. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - volatility: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: Parameters are not optional if the module has not accepted a derivative in its initialization. """ ( log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) = acquire_params_from_derivative_2( self.derivative, log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) return bs_lookback_price( log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, volatility=volatility, strike=self.strike, )
[docs] @torch.enable_grad() def delta( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, volatility: Optional[Tensor] = None, create_graph: bool = False, ) -> Tensor: """Returns delta of the derivative. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. volatility (torch.Tensor, optional): Volatility of the underlying asset. create_graph (bool, default=False): If True, graph of the derivative will be constructed. This option is used to compute gamma. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - volatility: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: Parameters are not optional if the module has not accepted a derivative in its initialization. """ ( log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) = acquire_params_from_derivative_2( self.derivative, log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) return super().delta( log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, volatility=volatility, create_graph=create_graph, strike=self.strike, )
[docs] @torch.enable_grad() def gamma( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, volatility: Optional[Tensor] = None, ) -> Tensor: """Returns gamma of the derivative. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. volatility (torch.Tensor, optional): Volatility of the underlying asset. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - volatility: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: Arguments are not optional if it doesn't accept derivative in this initialization. """ ( log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) = acquire_params_from_derivative_2( self.derivative, log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) return super().gamma( strike=self.strike, log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, volatility=volatility, )
[docs] @torch.enable_grad() def vega( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, volatility: Optional[Tensor] = None, ) -> Tensor: """Returns vega of the derivative. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. volatility (torch.Tensor, optional): Volatility of the underlying asset. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - volatility: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: - Arguments are not optional if it doesn't accept derivative in this initialization. """ ( log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) = acquire_params_from_derivative_2( self.derivative, log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) return super().vega( strike=self.strike, log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, volatility=volatility, )
[docs] @torch.enable_grad() def theta( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, volatility: Optional[Tensor] = None, ) -> Tensor: """Returns theta of the derivative. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. volatility (torch.Tensor, optional): Volatility of the underlying asset. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - volatility: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: - Risk-free rate is set to zero. - Arguments are not optional if it doesn't accept derivative in this initialization. """ ( log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) = acquire_params_from_derivative_2( self.derivative, log_moneyness, max_log_moneyness, time_to_maturity, volatility, ) return super().theta( strike=self.strike, log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, volatility=volatility, )
[docs] def implied_volatility( self, log_moneyness: Optional[Tensor] = None, max_log_moneyness: Optional[Tensor] = None, time_to_maturity: Optional[Tensor] = None, price: Optional[Tensor] = None, precision: float = 1e-6, ) -> Tensor: """Returns implied volatility of the derivative. Args: log_moneyness (torch.Tensor, optional): Log moneyness of the underlying asset. max_log_moneyness (torch.Tensor, optional): Cumulative maximum of the log moneyness. time_to_maturity (torch.Tensor, optional): Time to expiry of the option. price (torch.Tensor): Price of the derivative. precision (float, default=1e-6): Precision of the implied volatility. Shape: - log_moneyness: :math:`(N, *)` where :math:`*` means any number of additional dimensions. - max_log_moneyness: :math:`(N, *)` - time_to_maturity: :math:`(N, *)` - price: :math:`(N, *)` - output: :math:`(N, *)` Returns: torch.Tensor Note: - Arguments are not optional if it doesn't accept derivative in this initialization. """ # price seems optional in typing, but it isn't. It is set for the compatibility to the previous versions. (log_moneyness, time_to_maturity) = acquire_params_from_derivative_0( self.derivative, log_moneyness, time_to_maturity ) if price is None: raise ValueError( "price is required in this method. None is set only for compatibility to the previous versions." ) return find_implied_volatility( self.price, price=price, log_moneyness=log_moneyness, max_log_moneyness=max_log_moneyness, time_to_maturity=time_to_maturity, precision=precision, )
factory = BlackScholesModuleFactory() factory.register_module("LookbackOption", BSLookbackOption) # Assign docstrings so they appear in Sphinx documentation _set_docstring(BSLookbackOption, "inputs", BSModuleMixin.inputs) _set_attr_and_docstring(BSLookbackOption, "forward", BSModuleMixin.forward)