Skip to content

Commit

Permalink
Release/0.9.0 (#215)
Browse files Browse the repository at this point in the history
* BUG: Fix `time_to_maturity` mismatch (close #211) (#214)

* ENH: Add `VarianceSwap` (close #127) (#207)

* DOC: Fix code blocks in docstrings (#199)

* MAINT: Use `disable` in `tqdm` (#209)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <[email protected]>
  • Loading branch information
3 people committed Aug 17, 2021
1 parent f84dcff commit 45b2a81
Show file tree
Hide file tree
Showing 46 changed files with 527 additions and 222 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ $ pip install pfhedge
Financial instruments are provided in [`pfhedge.instruments`](https://pfnet-research.github.io/pfhedge/instruments.html) and classified into two types:

* **`Primary` instruments**: A primary instrument is a basic financial instrument that is traded on a market, and therefore their prices are accessible as the market prices. Examples include stocks, bonds, commodities, and currencies.
* **`Derivative` instruments**: A derivative is a financial instrument whose payoff is contingent on a primary instrument. An (over-the-counter) derivative is not traded on the market, and therefore the price is not directly accessible. Examples include [`EuropeanOption`](https://en.wikipedia.org/wiki/Option_style#American_and_European_options), [`LookbackOption`](https://en.wikipedia.org/wiki/Lookback_option), and so forth.
* **`Derivative` instruments**: A derivative is a financial instrument whose payoff is contingent on a primary instrument. An (over-the-counter) derivative is not traded on the market, and therefore the price is not directly accessible. Examples include [`EuropeanOption`](https://en.wikipedia.org/wiki/Option_style#American_and_European_options), [`LookbackOption`](https://en.wikipedia.org/wiki/Lookback_option), [`VarianceSwap`](https://en.wikipedia.org/wiki/Variance_swap), and so forth.

We consider a [`BrownianStock`](https://pfnet-research.github.io/pfhedge/generated/pfhedge.instruments.BrownianStock.html), which is a stock following the [geometric Brownian motion](https://en.wikipedia.org/wiki/Geometric_Brownian_motion), and a [`EuropeanOption`](https://pfnet-research.github.io/pfhedge/generated/pfhedge.instruments.EuropeanOption.html) which is contingent on it.
We assume that the stock has a transaction cost of 1 basis point.
Expand Down
1 change: 1 addition & 0 deletions docs/source/instruments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Derivative Instruments
instruments.LookbackOption
instruments.EuropeanBinaryOption
instruments.AmericanBinaryOption
instruments.VarianceSwap
2 changes: 2 additions & 0 deletions docs/source/nn.functional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ Other Functions
.. autofunction:: leaky_clamp
.. autofunction:: clamp
.. autofunction:: topp
.. autofunction:: realized_variance
.. autofunction:: realized_volatility
4 changes: 2 additions & 2 deletions pfhedge/_utils/bisect.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def bisect(
torch.Tensor
Raises:
RuntimeError: If the number of iteration exceeds `max_iter`.
RuntimeError: If the number of iteration exceeds ``max_iter``.
Examples:
Expand All @@ -62,7 +62,7 @@ def bisect(
raise ValueError("condition lower < upper should be satisfied.")

if (function(lower) > function(upper)).all():
# If `function` is a decreasing function
# If function is a decreasing function
mf = lambda input: -function(input)
return bisect(mf, -target, lower, upper, precision=precision, max_iter=max_iter)

Expand Down
2 changes: 1 addition & 1 deletion pfhedge/_utils/doc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def set_docstring(object: object, name: str, value: object) -> None:
# so that `object.name.__doc__ == value.__doc__`
# so that object.name.__doc__ == value.__doc__
setattr(getattr(object, name), "__doc__", value.__doc__)


Expand Down
2 changes: 1 addition & 1 deletion pfhedge/_utils/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
def save_prev_output(
module: Module, input: Optional[Tensor], output: Optional[Tensor]
) -> None:
"""A hook to save previous output as a buffer named `prev_output`.
"""A hook to save previous output as a buffer named ``prev_output``.
Examples:
Expand Down
1 change: 0 additions & 1 deletion pfhedge/_utils/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@


def has_lazy(module: Module) -> bool:
"""Returns `True` if a module has any `UninitializedParameter`."""
return any(map(is_lazy, module.parameters()))
19 changes: 10 additions & 9 deletions pfhedge/autogreek.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ def delta(
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.
**kwargs: Parameters passed to `pricer`.
create_graph (bool, default=False): If ``True``, graph of the derivative
will be constructed, allowing to compute higher order derivative
products.
**kwargs: Parameters passed to ``pricer``.
Returns:
torch.Tensor
Expand Down Expand Up @@ -58,17 +59,17 @@ def delta(
>>>
>>> _ = torch.manual_seed(42)
>>>
>>> derivative = EuropeanOption(BrownianStock(cost=1e-4))
>>> derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(torch.float64)
>>> model = WhalleyWilmott(derivative)
>>> hedger = Hedger(model, model.inputs())
>>> 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.52...)
tensor(0.5...)
"""
if kwargs.get("strike") is None and kwargs.get("spot") is None:
# Since delta does not depend on strike,
Expand All @@ -81,7 +82,7 @@ def delta(
kwargs["moneyness"] = spot / kwargs["strike"]
kwargs["log_moneyness"] = (spot / kwargs["strike"]).log()

# Delete parameters that are not in the signature of `pricer` to avoid
# Delete parameters that are not in the signature of pricer to avoid
# TypeError: <pricer> got an unexpected keyword argument '<parameter>'
for parameter in list(kwargs.keys()):
if parameter not in signature(pricer).parameters.keys():
Expand All @@ -107,9 +108,9 @@ def gamma(
Args:
pricer (callable): Pricing formula of a derivative.
create_graph (bool, default=False): If `True`, graph of the derivative will be
create_graph (bool, default=False): If ``True``, graph of the derivative will be
constructed, allowing to compute higher order derivative products.
**kwargs: Parameters passed to `pricer`.
**kwargs: Parameters passed to ``pricer``.
Returns:
torch.Tensor
Expand Down
2 changes: 1 addition & 1 deletion pfhedge/features/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __getitem__(self, i: int) -> Tensor:
"""

def of(self: T, derivative=None, hedger=None) -> T:
"""Set `derivative` and `hedger` to the attributes of `self`.
"""Set ``derivative`` and ``hedger`` to the attributes of ``self``.
Args:
derivative (Derivative, optional): The derivative to compute features.
Expand Down
1 change: 0 additions & 1 deletion pfhedge/features/_getter.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,4 @@ def get_feature(feature: Union[str, Feature]) -> Feature:
feature = dict_features[feature]
elif not isinstance(feature, Feature):
raise TypeError(f"{feature} is not an instance of Feature.")
# If `feature` is Feature object, pass it through.
return feature
31 changes: 16 additions & 15 deletions pfhedge/features/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Moneyness(Feature):
"""Moneyness of the underlying instrument of the derivative.
Args:
log (bool, default=False): If `True`, represents log moneyness.
log (bool, default=False): If ``True``, represents log moneyness.
"""

def __init__(self, log: bool = False) -> None:
Expand All @@ -30,9 +30,9 @@ def __str__(self) -> str:

def __getitem__(self, i: int) -> Tensor:
if self.log:
return self.derivative.log_moneyness(i).unsqueeze(-1)
return self.derivative.log_moneyness(i)
else:
return self.derivative.moneyness(i).unsqueeze(-1)
return self.derivative.moneyness(i)


class LogMoneyness(Moneyness):
Expand All @@ -49,7 +49,7 @@ def __str__(self) -> str:
return "expiry_time"

def __getitem__(self, i: int) -> Tensor:
return self.derivative.time_to_maturity(i).unsqueeze(-1)
return self.derivative.time_to_maturity(i)


class Volatility(Feature):
Expand All @@ -74,14 +74,14 @@ def __getitem__(self, i: int) -> Tensor:

class Barrier(Feature):
"""A feature which signifies whether the price of the underlier have reached
the barrier. The output `1.0` means that the price have touched the barrier,
and `0` otherwise.
the barrier. The output 1.0 means that the price have touched the barrier,
and 0.0 otherwise.
Args:
threshold (float): The price level of the barrier.
up (bool, default True): If `True`, signifies whether the price has exceeded
up (bool, default True): If ``True``, signifies whether the price has exceeded
the barrier upward.
If `False`, signifies whether the price has exceeded the barrier downward.
If ``False``, signifies whether the price has exceeded the barrier downward.
"""

def __init__(self, threshold: float, up: bool = True) -> None:
Expand Down Expand Up @@ -122,7 +122,7 @@ class MaxMoneyness(Feature):
"""Cumulative maximum of moneyness.
Args:
log (bool, default=False): If `True`, represents log moneyness.
log (bool, default=False): If ``True``, represents log moneyness.
"""

def __init__(self, log: bool = False) -> None:
Expand All @@ -147,15 +147,16 @@ def __init__(self) -> None:


class ModuleOutput(Feature, Module):
"""The feature computed as an output of a `torch.nn.Module`.
"""The feature computed as an output of a ``torch.nn.Module``.
Args:
module (torch.nn.Module): Module to compute the value of the feature.
The input and output shapes should be `(N, *, H_in) -> (N, *, 1)`,
where `N` stands for the number of Monte Carlo paths of the underlier of
the derivative, `H_in` stands for the number of input features
(namely, `H_in = len(inputs)`),
and `*` means any number of additional dimensions.
The input and output shapes should be :math:`(N, *, H_in) -> (N, *, 1)`,
where :math:`N` stands for the number of Monte Carlo paths of
the underlier of the derivative,
:math:`H_in` stands for the number of input features
(namely, ``len(inputs)``),
and :math:`*` means any number of additional dimensions.
inputs (list[Feature]): The input features to the module.
Examples:
Expand Down
1 change: 1 addition & 0 deletions pfhedge/instruments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .derivative.european import EuropeanOption
from .derivative.european_binary import EuropeanBinaryOption
from .derivative.lookback import LookbackOption
from .derivative.variance_swap import VarianceSwap
from .primary.base import Primary
from .primary.brownian import BrownianStock
from .primary.heston import HestonStock
24 changes: 12 additions & 12 deletions pfhedge/instruments/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def to(self: T, *args, **kwargs) -> T:
"""Performs dtype and/or device conversion of the buffers associated to
the instument.
A `torch.dtype` and `torch.device` are inferred from the arguments of
`self.to(*args, **kwargs)`.
A ``torch.dtype`` and ``torch.device`` are inferred from the arguments of
``self.to(*args, **kwargs)``.
Args:
dtype (torch.dtype): Desired floating point type of the floating point
Expand Down Expand Up @@ -69,43 +69,43 @@ def cuda(self: T, device: Optional[int] = None) -> T:
)

def double(self: T) -> T:
"""`self.double()` is equivalent to `self.to(torch.float64)`.
"""It is equivalent to ``self.to(torch.float64)``.
See :func:`to()`.
"""
return self.to(torch.float64)

def float(self: T) -> T:
"""`self.float()` is equivalent to `self.to(torch.float32)`.
"""It is equivalent to ``self.to(torch.float32)``.
See :func:`to()`.
"""
return self.to(torch.float32)

def half(self: T) -> T:
"""`self.half()` is equivalent to `self.to(torch.float16)`.
"""It is equivalent to ``self.to(torch.float16)``.
See :func:`to()`.
"""
return self.to(torch.float16)

def bfloat16(self: T) -> T:
"""`self.bfloat16()` is equivalent to `self.to(torch.bfloat16)`.
"""It is equivalent to ``self.to(torch.bfloat16)``.
See :func:`to()`.
"""
return self.to(torch.bfloat16)

@property
def dinfo(self) -> List[str]:
"""Returns list of strings that tell `dtype` and `device` of `self`.
"""Returns list of strings that tell ``dtype`` and ``device`` of self.
Intended to be used in `__repr__`.
Intended to be used in :func:`__repr__`.
If `dtype` (`device`) is the one specified in default type,
`dinfo` will not have the information of it.
If ``dtype`` (``device``) is the one specified in default type,
``dinfo`` will not have the information of it.
Returns:
list[str]
"""
# Implementation here refers to the function `_str_intern` in
# `pytorch/_tensor_str.py`.
# Implementation here refers to the function _str_intern in
# pytorch/_tensor_str.py.

dinfo = []

Expand Down
10 changes: 5 additions & 5 deletions pfhedge/instruments/derivative/american_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ class AmericanBinaryOption(Derivative, OptionMixin):
maturity (float, default=20/250): The maturity of the option.
dtype (torch.device, optional): Desired device of returned tensor.
Default: If None, uses a global default
(see `torch.set_default_tensor_type()`).
(see ``torch.set_default_tensor_type()``).
device (torch.device, optional): Desired device of returned tensor.
Default: if None, uses the current device for the default tensor type
(see `torch.set_default_tensor_type()`).
`device` will be the CPU for CPU tensor types and
(see ``torch.set_default_tensor_type()``).
``device`` will be the CPU for CPU tensor types and
the current CUDA device for CUDA tensor types.
Attributes:
Expand All @@ -78,8 +78,8 @@ class AmericanBinaryOption(Derivative, OptionMixin):
maturity=5/250, strike=1.01)
>>> derivative.simulate(n_paths=2)
>>> derivative.underlier.spot
tensor([[1.0000, 1.0016, 1.0044, 1.0073, 0.9930],
[1.0000, 1.0282, 1.0199, 1.0258, 1.0292]])
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]])
>>> derivative.payoff()
tensor([0., 1.])
"""
Expand Down
33 changes: 15 additions & 18 deletions pfhedge/instruments/derivative/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def simulate(
n_paths (int): The number of paths to simulate.
init_state (tuple[torch.Tensor | float], optional): The initial state of
the underlier.
**kwargs: Other parameters passed to `self.underlier.simulate()`.
**kwargs: Other parameters passed to ``self.underlier.simulate()``.
"""
self.underlier.simulate(
n_paths=n_paths, time_horizon=self.maturity, init_state=init_state
Expand Down Expand Up @@ -96,34 +96,32 @@ def moneyness(self, time_step: Optional[int] = None) -> Tensor:
Args:
time_step (int, optional): The time step to calculate
the moneyness. If `None` (default), the moneyness is calculated
the moneyness. If ``None`` (default), the moneyness is calculated
at all time steps.
Shape:
- Output: :math:`(N, T)` where :math:`N` is the number of paths and
:math:`T` is the number of time steps.
If `time_step` is given, the shape is :math:`(N, 1)`.
If ``time_step`` is given, the shape is :math:`(N, 1)`.
Returns:
torch.Tensor
"""
spot = self.underlier.spot
if time_step is not None:
spot = spot[..., time_step]
return spot / self.strike
index = ... if time_step is None else [time_step]
return self.underlier.spot[..., index] / self.strike

def log_moneyness(self, time_step: Optional[int] = None) -> Tensor:
"""Returns the log moneyness of self.
Args:
time_step (int, optional): The time step to calculate the log
moneyness. If `None` (default), the moneyness is calculated
moneyness. If ``None`` (default), the moneyness is calculated
at all time steps.
Shape:
- Output: :math:`(N, T)` where :math:`N` is the number of paths and
:math:`T` is the number of time steps.
If `time_step` is given, the shape is :math:`(N, 1)`.
If ``time_step`` is given, the shape is :math:`(N, 1)`.
Returns:
torch.Tensor
Expand All @@ -135,27 +133,26 @@ def time_to_maturity(self, time_step: Optional[int] = None) -> Tensor:
Args:
time_step (int, optional): The time step to calculate
the time to maturity. If `None` (default), the time to
the time to maturity. If ``None`` (default), the time to
maturity is calculated at all time steps.
Shape:
- Output: :math:`(N, T)` where :math:`N` is the number of paths and
:math:`T` is the number of time steps.
If `time_step` is given, the shape is :math:`(N, 1)`.
If ``time_step`` is given, the shape is :math:`(N, 1)`.
Returns:
torch.Tensor
"""
n_paths, n_steps = self.underlier.spot.size()
if time_step is None:
t = self.underlier.dt * torch.arange(n_steps).repeat(n_paths, 1)
t.to(self.underlier.device)
return self.maturity - t
# Time passed from the beginning
t = torch.arange(n_steps).to(self.underlier.spot) * self.underlier.dt
return (t[-1] - t).unsqueeze(0).expand(n_paths, -1)
else:
t = torch.full_like(
self.underlier.spot[:, 0], time_step * self.underlier.dt
)
return self.maturity - t
time = n_steps - (time_step % n_steps) - 1
t = torch.tensor([[time]]).to(self.underlier.spot) * self.underlier.dt
return t.expand(n_paths, -1)


# Assign docstrings so they appear in Sphinx documentation
Expand Down
Loading

0 comments on commit 45b2a81

Please sign in to comment.