Hedger

class pfhedge.nn.Hedger(model, inputs, criterion=EntropicRiskMeasure())[source]

Module to hedge and price derivatives.

References

  • Buehler, H., Gonon, L., Teichmann, J. and Wood, B., 2019. Deep hedging. Quantitative Finance, 19(8), pp.1271-1291. [arXiv:1802.03042 [q-fin]]

Parameters
  • model (torch.nn.Module) – Hedging model to compute the hedge ratio at the next time step from the input features at the current time step. The input and output shapes should be \((N, F)\) and \((N, H)\) respectively, where \(N\) stands for the number simulated paths of the asset prices and \(F\) is the number of input features (len(inputs)), and \(H\) is the number of hedging instruments.

  • inputs (list[str|Feature]) – List of the names of the input features that will be fed to the model. See pfhedge.features.list_feature_names() for available feature names and see pfhedge.features for the details of features.

  • criterion (HedgeLoss, default=EntropicRiskMeasure()) – Loss function to minimize by hedging. Default: pfhedge.nn.EntropicRiskMeasure() .

Shape:
  • input: \((N, F)\) where \(N\) is the number of simulated paths and \(F\) is the number of input features.

  • output: \((N, H)\) where \(H\) is the number of hedging instruments.

Examples

A hedger that uses Black-Scholes’ delta hedging strategy. See pfhedge.nn.BlackScholes for details of the module.

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import BlackScholes
>>> from pfhedge.nn import Hedger
...
>>> derivative = EuropeanOption(BrownianStock(cost=1e-4))
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger
Hedger(
  inputs=['log_moneyness', 'time_to_maturity', 'volatility']
  (model): BSEuropeanOption(strike=1.)
  (criterion): EntropicRiskMeasure()
)

A hedger that uses Whalley-Wilmott’s no-transaction-band strategy. See pfhedge.nn.WhalleyWilmott for details of the module.

>>> from pfhedge.nn import WhalleyWilmott
>>>
>>> model = WhalleyWilmott(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger
Hedger(
  inputs=['log_moneyness', 'time_to_maturity', 'volatility', 'prev_hedge']
  (model): WhalleyWilmott(
    (bs): BSEuropeanOption(strike=1.)
  )
  (criterion): EntropicRiskMeasure()
)

A hedger that takes naked positions (never hedge at all). See pfhedge.nn.Naked for details of the module.

>>> from pfhedge.nn import Naked
>>>
>>> hedger = Hedger(Naked(), ["empty"])

A hedger represented by a neural network (Deep Hedging). See pfhedge.nn.MultiLayerPerceptron for details of the module.

>>> from pfhedge.nn import MultiLayerPerceptron
>>>
>>> model = MultiLayerPerceptron()
>>> hedger = Hedger(model, ["moneyness", "time_to_maturity", "volatility"])
>>> derivative.simulate(n_paths=1)
>>> _ = hedger.compute_pl(derivative)  # Lazily materialize
>>> hedger
Hedger(
  inputs=['moneyness', 'time_to_maturity', 'volatility']
  (model): MultiLayerPerceptron(
    (0): Linear(in_features=3, out_features=32, bias=True)
    (1): ReLU()
    (2): Linear(in_features=32, out_features=32, bias=True)
    (3): ReLU()
    (4): Linear(in_features=32, out_features=32, bias=True)
    (5): ReLU()
    (6): Linear(in_features=32, out_features=32, bias=True)
    (7): ReLU()
    (8): Linear(in_features=32, out_features=1, bias=True)
    (9): Identity()
  )
  (criterion): EntropicRiskMeasure()
)
>>> history = hedger.fit(derivative, n_paths=1, n_epochs=1, verbose=False)
>>> hedger.price(derivative)
tensor(...)

It is possible to hedge a derivative with another listed derivative by list() method.

>>> from pfhedge.instruments import LookbackOption
>>> from pfhedge.nn import BlackScholes
>>>
>>> pricer = lambda derivative: BlackScholes(derivative).price(
...     log_moneyness=derivative.log_moneyness(),
...     time_to_maturity=derivative.time_to_maturity(),
...     volatility=derivative.ul().volatility)
>>>
>>> stock = BrownianStock()
>>> hedging_instrument = EuropeanOption(stock, maturity=5/250)
>>> hedging_instrument.list(pricer, cost=1e-4)
>>> derivative = LookbackOption(stock)
>>>
>>> hedger = Hedger(
...     MultiLayerPerceptron(),
...     inputs=["moneyness", "time_to_maturity", "volatility"])
>>> _ = hedger.fit(
...     derivative,
...     hedge=[hedging_instrument],
...     n_paths=1,
...     n_epochs=1,
...     verbose=False)
>>> hedger.price(derivative)
tensor(...)

Hedging a derivative with multiple instruments.

>>> from pfhedge.instruments import HestonStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.instruments import VarianceSwap
>>> from pfhedge.nn import BlackScholes
>>>
>>> _ = torch.manual_seed(42)
>>> stock = HestonStock(cost=1e-4)
>>> option = EuropeanOption(stock)
>>> varswap = VarianceSwap(stock)
>>> pricer = lambda varswap: varswap.ul().variance - varswap.strike
>>> varswap.list(pricer, cost=1e-4)
>>> hedger = Hedger(
...     MultiLayerPerceptron(3, 2),
...     inputs=["moneyness", "time_to_maturity", "volatility"])
>>> hedger.price(option, hedge=[stock, varswap], n_paths=2)
tensor(...)
compute_hedge(derivative, hedge=None)[source]

Compute the hedge ratio at each time step.

This method assumes that the derivative is already simulated.

Parameters
  • derivative (BaseDerivative) – The derivative to hedge.

  • hedge (list[BaseInstrument], optional) – The hedging instruments. If None (default), use derivative.underliers.

Shape:
  • output: \((N, H, T)\) where \(N\) is the number of paths, \(H\) is the number of hedging instruments, and \(T\) is the number of time steps.

Returns

torch.Tensor

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import BlackScholes
...
>>> _ = torch.manual_seed(42)
>>> derivative = EuropeanOption(BrownianStock(), maturity=5/250)
>>> derivative.simulate(n_paths=2)
>>> derivative.ul().spot
tensor([[1.0000, 1.0016, 1.0044, 1.0073, 0.9930, 0.9906],
        [1.0000, 0.9919, 0.9976, 1.0009, 1.0076, 1.0179]])
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger.compute_hedge(derivative).squeeze(1)
tensor([[0.5056, 0.5295, 0.5845, 0.6610, 0.2918, 0.2918],
        [0.5056, 0.3785, 0.4609, 0.5239, 0.7281, 0.7281]])
compute_loss(derivative, hedge=None, n_paths=1000, n_times=1, init_state=None, enable_grad=True)[source]

Returns the value of the criterion for the terminal portfolio value after hedging a given derivative.

This method basically computes self.criterion(pl) where pl is given by compute_pl().

Parameters
  • derivative (BaseDerivative) – The derivative to hedge.

  • hedge (list[BaseInstrument], optional) – The hedging instruments. If None (default), use [derivative.underlier].

  • n_paths (int, default=1000) – The number of simulated price paths of the underlying instrument.

  • n_times (int, default=1) – If n_times > 1, returns the ensemble mean of the losses computed through multiple simulations.

  • init_state (tuple, optional) – The initial price of the underlying instrument of the derivative. If None (default), it uses the default value of the underlying instrument.

  • enable_grad (bool, default=True) – Context-manager that sets gradient calculation to on or off.

Shape:
  • Output: \(()\)

Returns

torch.Tensor

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import BlackScholes
>>> from pfhedge.nn import Hedger
...
>>> derivative = EuropeanOption(BrownianStock())
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger.compute_loss(derivative, n_paths=2)
tensor(...)

One can use PyTorch built-in loss functions, such as the mean squared loss torch.nn.MSELoss, as criteria. Then the criterion measures the loss between the hedging portfolio (cf. compute_portfolio()) as input and the payoff of the derivative as target.

>>> from torch.nn import MSELoss
...
>>> _ = torch.manual_seed(42)
>>> derivative = EuropeanOption(BrownianStock())
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs(), criterion=MSELoss())
>>> hedger.compute_loss(derivative, n_paths=10)
tensor(...)
compute_pl(derivative, hedge=None)[source]

Returns the terminal portfolio value after hedging a given derivative.

This method assumes that the derivative is already simulated.

See pfhedge.nn.functional.terminal_value() for the expression of the terminal portfolio value after hedging a derivative.

Parameters
  • derivative (BaseDerivative) – The derivative to hedge.

  • hedge (list[BaseInstrument], optional) – The hedging instruments. If None (default), use [derivative.underlier].

  • n_paths (int, default=1000) – The number of simulated price paths of the underlying instrument.

  • init_state (tuple[torch.Tensor | float], optional) – The initial state of the underlying instrument of the derivative. If None (default), it uses the default value.

Shape:
  • Output: \((N)\) where \(N\) is the number of paths.

Returns

torch.Tensor

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import BlackScholes
>>> from pfhedge.nn import Hedger
...
>>> derivative = EuropeanOption(BrownianStock())
>>> derivative.simulate(n_paths=2)
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger.compute_pl(derivative)
tensor([..., ...])
compute_pnl(derivative, hedge=None, n_paths=1000, init_state=None)[source]

(deprecated) Simulates derivative and computes profit loss by compute_pl().

compute_portfolio(derivative, hedge=None)[source]

Compute terminal value of the hedging portfolio.

See pfhedge.nn.functional.pl(), with \(Z\) being substituted with 0, for the expression of the terminal value of the hedging portfolio.

This method assumes that the derivative is already simulated.

Parameters
  • derivative (BaseDerivative) – The derivative to hedge.

  • hedge (BaseInstrument, optional) – The hedging instrument. If None (default), use derivative.underlier.

Shape:
  • output: \((N)\) where \(N\) is the number of paths.

Returns

torch.Tensor

fit(derivative, hedge=None, n_epochs=100, n_paths=1000, n_times=1, optimizer=<class 'torch.optim.adam.Adam'>, init_state=None, verbose=True, validation=True, tqdm_kwargs={})[source]

Fit the hedging model to hedge a given derivative.

The training is performed so that the hedger minimizes criterion(pl) where pl is given by compute_pl().

It returns the training history, that is, validation loss after each simulation.

Parameters
  • derivative (BaseDerivative) – The derivative to hedge.

  • hedge (list[BaseInstrument], optional) – The hedging instruments. If None (default), use [derivative.underlier].

  • n_epochs (int, default=100) – Number of Monte-Carlo simulations.

  • n_paths (int, default=1000) – The number of simulated price paths of the underlying instrument.

  • n_times (int, default=1) – If n_times > 1, returns the ensemble mean of the losses computed through multiple simulations.

  • optimizer (torch.optim.Optimizer, default=Adam) – The optimizer algorithm to use. It can be an instance or a class of torch.optim.Optimizer.

  • init_state (tuple, optional) – The initial price of the underlying instrument of the derivative. If None (default), sensible default value is used.

  • verbose (bool, default=True) – If True, print progress of the training to 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]

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import MultiLayerPerceptron
...
>>> derivative = EuropeanOption(BrownianStock())
>>> model = MultiLayerPerceptron()
>>> hedger = Hedger(model, ["moneyness", "time_to_maturity", "volatility"])
>>> history = hedger.fit(derivative, n_paths=1, n_epochs=1, verbose=False)

One can use a custom optimizer as follows.

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import MultiLayerPerceptron
>>> from torch.optim import SGD
...
>>> derivative = EuropeanOption(BrownianStock())
>>> hedger = Hedger(MultiLayerPerceptron(), ["empty"])
>>> # Run a placeholder forward to initialize lazy parameters
>>> _ = hedger.compute_pnl(derivative, n_paths=1)
>>> _ = hedger.fit(
...     derivative,
...     optimizer=SGD(hedger.parameters(), lr=0.1),
...     n_epochs=1,
...     verbose=False)

One can also pass a class object of an optimizer. The optimizer will be initialized as Adadelta(hedger.parameters()).

>>> from torch.optim import Adadelta
...
>>> derivative = EuropeanOption(BrownianStock())
>>> hedger = Hedger(MultiLayerPerceptron(), ["empty"])
>>> _ = hedger.fit(
...     derivative,
...     optimizer=Adadelta,
...     n_epochs=1,
...     verbose=False)
forward(input)[source]

Returns the outout of self.model.

The output represents the hedge ratio at the next time step.

get_input(derivative, time_step)[source]

Returns the input tensor to the model at the given time step.

Note

This method assumes that a derivative is already registered to the features. If self has not yet hedged a derivative, run a placeholder computation _ = self.compute_pnl(derivative, n_paths=1) before calling this method.

Parameters
  • derivative (BaseDerivative) – The derivative used for getting the input.

  • time_step (int, optional) – The time step to get the input tensor. If None an input tensor for all time steps is returned.

Shape:
  • Output: \((N, T, F)\) where \(N\) is the number of paths, \(T\) is the number of time steps, and \(F\) is the number of input features. If time_step is specified, \(T = 1\).

Returns

torch.Tensor

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import Naked
...
>>> derivative = EuropeanOption(BrownianStock())
>>> derivative.simulate()
>>> hedger = Hedger(Naked(), ["time_to_maturity", "volatility"])
>>> _ = hedger.compute_pnl(derivative, n_paths=1)  # Materialize features
>>> hedger.get_input(derivative, 0)
tensor([[[0.0800, 0.2000]]])
price(derivative, hedge=None, n_paths=1000, n_times=1, init_state=None, enable_grad=False)[source]

Evaluate the premium of the given derivative.

Parameters
  • derivative (BaseDerivative) – The derivative to price.

  • hedge (list[BaseInstrument], optional) – The hedging instruments. If None (default), use [derivative.underlier].

  • n_paths (int, default=1000) – The number of simulated price paths of the underlying instrument.

  • n_times (int, default=1) – If n_times > 1, returns the ensemble mean of the losses computed through multiple simulations.

  • init_state (tuple, optional) – The initial price of the underlying instrument of the derivative. If None (default), it uses the default value of the underlying instrument.

  • enable_grad (bool, default=False) – Context-manager that sets gradient calculation to on or off.

Shape:
  • Output: \(()\)

Returns

torch.Tensor

Examples

>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
>>> from pfhedge.nn import BlackScholes
>>> from pfhedge.nn import Hedger
...
>>> derivative = EuropeanOption(BrownianStock())
>>> model = BlackScholes(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> hedger.price(derivative, n_paths=2)
tensor(...)