Skip to content

Commit

Permalink
Release/0.6.1 (#118)
Browse files Browse the repository at this point in the history
* REF: Refactor features (#111) (#113)

* DOC: Add examples in docstrings (#115)

* ENH: Run a placeholder forward only when a hedger is lazy (#116)
  • Loading branch information
simaki committed Jun 19, 2021
1 parent 11c86c2 commit 0518c9a
Show file tree
Hide file tree
Showing 28 changed files with 263 additions and 106 deletions.
17 changes: 17 additions & 0 deletions pfhedge/_utils/lazy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from torch.nn import Module
from torch.nn.parameter import is_lazy


def has_lazy(module: Module) -> bool:
"""Returns `True` if a module has any `UninitializedParameter`.
Args:
module (torch.nn.Module):
Returns:
bool
"""
for t in module.parameters():
if is_lazy(t):
return True
return False
3 changes: 2 additions & 1 deletion pfhedge/features/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ._getter import FEATURES
from ._getter import get_feature
from .features import Barrier
from .features import Empty
from .features import ExpiryTime
from .features import LogMoneyness
from .features import MaxLogMoneyness
Expand All @@ -9,4 +10,4 @@
from .features import Moneyness
from .features import PrevHedge
from .features import Volatility
from .features import Zero
from .features import Zeros
6 changes: 4 additions & 2 deletions pfhedge/features/_getter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from ._base import Feature
from .features import Empty
from .features import ExpiryTime
from .features import LogMoneyness
from .features import MaxLogMoneyness
from .features import MaxMoneyness
from .features import Moneyness
from .features import PrevHedge
from .features import Volatility
from .features import Zero
from .features import Zeros

FEATURES = [
ExpiryTime(),
Expand All @@ -16,7 +17,8 @@
Moneyness(),
PrevHedge(),
Volatility(),
Zero(),
Zeros(),
Empty(),
]


Expand Down
76 changes: 38 additions & 38 deletions pfhedge/features/features.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import torch

from ._base import Feature
from .functional import barrier
from .functional import empty
from .functional import expiry_time
from .functional import log_moneyness
from .functional import max_log_moneyness
from .functional import max_moneyness
from .functional import moneyness
from .functional import prev_hedge
from .functional import volatility
from .functional import zeros


class Moneyness(Feature):
Expand All @@ -22,12 +32,10 @@ def __str__(self):
return "moneyness"

def __getitem__(self, i):
# spot price: shape (N, 1)
s = self.derivative.underlier.spot[..., i].reshape(-1, 1)
output = s / self.derivative.strike
if self.log:
output = torch.log(output)
return output
return log_moneyness(i, derivative=self.derivative)
else:
return moneyness(i, derivative=self.derivative)


class LogMoneyness(Moneyness):
Expand All @@ -44,8 +52,7 @@ def __str__(self):
return "expiry_time"

def __getitem__(self, i):
value = self.derivative.maturity - i * self.derivative.underlier.dt
return torch.full_like(self.derivative.underlier.spot[:, :1], value)
return expiry_time(i, derivative=self.derivative)


class Volatility(Feature):
Expand All @@ -55,8 +62,7 @@ def __str__(self):
return "volatility"

def __getitem__(self, i):
value = self.derivative.underlier.volatility
return torch.full_like(self.derivative.underlier.spot[:, :1], value)
return volatility(i, derivative=self.derivative)


class PrevHedge(Feature):
Expand All @@ -66,11 +72,7 @@ def __str__(self):
return "prev_hedge"

def __getitem__(self, i):
if hasattr(self.hedger, "prev_output"):
return self.hedger.prev_output.reshape(-1, 1)
else:
# spot: shape (N, T)
return torch.zeros_like(self.derivative.underlier.spot[:, :1])
return prev_hedge(i, derivative=self.derivative, hedger=self.hedger)


class Barrier(Feature):
Expand All @@ -79,40 +81,45 @@ class Barrier(Feature):
and `0` otherwise.
Args:
barrier (float): The price level of the barrier.
threshold (float): The price level of the barrier.
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.
"""

def __init__(self, barrier, up=True):
def __init__(self, threshold, up=True):
super().__init__()

self.barrier = barrier
self.threshold = threshold
self.up = up

def __repr__(self):
return self.__class__.__name__ + f"({self.barrier}, up={self.up})"
return self.__class__.__name__ + f"({self.threshold}, up={self.up})"

def __getitem__(self, i):
if self.up:
# shape: (N, i)
touch_barrier = self.derivative.underlier.spot[..., : i + 1] >= self.barrier
else:
touch_barrier = self.derivative.underlier.spot[..., : i + 1] <= self.barrier
return touch_barrier.any(dim=-1, keepdim=True).to(
self.derivative.underlier.spot
return barrier(
i, derivative=self.derivative, threshold=self.threshold, up=self.up
)


class Zero(Feature):
class Zeros(Feature):
"""A feature of which value is always zero."""

def __str__(self):
return "zero"
return "zeros"

def __getitem__(self, i):
return torch.zeros_like(self.derivative.underlier.spot[:, :1])
return zeros(i, derivative=self.derivative)


class Empty(Feature):
"""A feature of which value is always empty."""

def __str__(self):
return "empty"

def __getitem__(self, i):
return empty(i, derivative=self.derivative)


class MaxMoneyness(Feature):
Expand All @@ -124,7 +131,6 @@ class MaxMoneyness(Feature):

def __init__(self, log=False):
self.log = log
self.moneyness = Moneyness(log=log)

def __str__(self):
if self.log:
Expand All @@ -133,16 +139,10 @@ def __str__(self):
return "max_moneyness"

def __getitem__(self, i):
s = self.derivative.underlier.spot[..., : i + 1].max(dim=1, keepdim=True).values
output = s / self.derivative.strike
if self.log:
output = torch.log(output)
return output

def of(self, derivative=None, hedger=None):
super().of(derivative)
self.moneyness.of(derivative=derivative, hedger=hedger)
return self
return max_log_moneyness(i, derivative=self.derivative)
else:
return max_moneyness(i, derivative=self.derivative)


class MaxLogMoneyness(MaxMoneyness):
Expand Down
70 changes: 70 additions & 0 deletions pfhedge/features/functional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import torch
from torch import Tensor
from torch.nn import Module


def moneyness(i: int, derivative: "Derivative", hedger: Module = None) -> Tensor:
"""Returns moneyness.
Args:
i (int): The index of time step.
derivative (pfhedge.instruments.Derivative):
hedger (pfhedge.nn.Hedger):
Returns:
torch.Tensor
"""
s = derivative.underlier.spot[..., [i]]
k = derivative.strike
return s / k


def log_moneyness(i, derivative, hedger=None) -> Tensor:
return moneyness(i, derivative=derivative).log()


def expiry_time(i, derivative, hedger=None) -> Tensor:
value = derivative.maturity - i * derivative.underlier.dt
return torch.full_like(derivative.underlier.spot[:, :1], value)


def volatility(i, derivative, hedger=None) -> Tensor:
value = derivative.underlier.volatility
return torch.full_like(derivative.underlier.spot[:, :1], value)


def prev_hedge(i, derivative, hedger) -> Tensor:
if hasattr(hedger, "prev_output"):
return hedger.prev_output
else:
# spot: shape (N, T)
return torch.zeros_like(derivative.underlier.spot[:, :1])


def barrier(
i, derivative, hedger=None, threshold: float = 1.0, up: bool = True
) -> Tensor:
if up:
# shape: (N, i)
touch_threshold = derivative.underlier.spot[..., : i + 1] >= threshold
else:
touch_threshold = derivative.underlier.spot[..., : i + 1] <= threshold
return touch_threshold.any(dim=-1, keepdim=True).to(derivative.underlier.spot)


def zeros(i, derivative, hedger=None) -> Tensor:
return torch.zeros_like(derivative.underlier.spot[:, :1])


def empty(i, derivative, hedger=None) -> Tensor:
return torch.empty_like(derivative.underlier.spot[:, :1])


def max_moneyness(i, derivative, hedger=None) -> Tensor:
m = derivative.underlier.spot[..., : i + 1].max(dim=1, keepdim=True).values
k = derivative.strike
return m / k


def max_log_moneyness(i, derivative, hedger=None) -> Tensor:
return max_moneyness(i, derivative=derivative).log()
3 changes: 2 additions & 1 deletion pfhedge/instruments/american_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class AmericanBinaryOption(Derivative):
strike (float, default=1.0): The strike price of the option.
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()`).
Default: If None, uses a global default
(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()`).
Expand Down
3 changes: 2 additions & 1 deletion pfhedge/instruments/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def dinfo(self) -> list:
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
5 changes: 3 additions & 2 deletions pfhedge/instruments/european.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class EuropeanOption(Derivative):
strike (float, default=1.0): The strike price of the option.
maturity (float, default=20/250): The maturity of the option.
dtype (torch.dtype, optional): Desired device of returned tensor.
Default: If None, uses a global default (see `torch.set_default_tensor_type()`).
Default: If None, uses a global default
(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()`).
Expand Down Expand Up @@ -63,7 +64,7 @@ class EuropeanOption(Derivative):
>>> deriv = EuropeanOption(BrownianStock())
>>> deriv.to(dtype=torch.float64, device="cuda:0")
EuropeanOption(BrownianStock(...), ..., dtype=torch.float64, device='cuda:0')
EuropeanOption(..., dtype=torch.float64, device='cuda:0')
"""

def __init__(
Expand Down
3 changes: 2 additions & 1 deletion pfhedge/instruments/european_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class EuropeanBinaryOption(Derivative):
strike (float, default=1): The strike price of the option.
maturity (float, default=20/250) The maturity of the option.
dtype (torch.dtype, optional): Desired device of returned tensor.
Default: If None, uses a global default (see `torch.set_default_tensor_type()`).
Default: If None, uses a global default
(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()`).
Expand Down
3 changes: 2 additions & 1 deletion pfhedge/instruments/lookback.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class LookbackOption(Derivative):
strike (float, default=1.0): The strike price of the option.
maturity (float, default=20/250): The maturity of the option.
dtype (torch.dtype, optional): Desired device of returned tensor.
Default: If None, uses a global default (see `torch.set_default_tensor_type()`).
Default: If None, uses a global default
(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()`).
Expand Down
26 changes: 18 additions & 8 deletions pfhedge/instruments/underlier.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class BrownianStock(Primary):
cost (float, default=0.0): The transaction cost rate.
dt (float, default=1/250): The intervals of the time steps.
dtype (torch.device, optional): Desired device of returned tensor.
Default: If None, uses a global default (see `torch.set_default_tensor_type()`).
Default: If None, uses a global default
(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()`).
Expand All @@ -26,16 +27,16 @@ class BrownianStock(Primary):
Attributes:
spot (torch.Tensor): The spot prices of the instrument.
This attribute is set by a method `simulate()`.
The shape is :math:`(T, N)`, where :math:`T` is the number of time steps and
The shape is :math:`(N, T)`, where :math:`T` is the number of time steps and
:math:`N` is the number of simulated paths.
Examples:
>>> import torch
>>> from pfhedge.instruments import BrownianStock
>>>
>>> _ = torch.manual_seed(42)
>>> stock = BrownianStock(volatility=0.20)
>>> stock.simulate(time_horizon=5 / 250, n_paths=2)
>>> stock = BrownianStock()
>>> stock.simulate(n_paths=2, time_horizon=5 / 250)
>>> stock.spot
tensor([[1.0000, 1.0016, 1.0044, 1.0073, 0.9930],
[1.0000, 1.0282, 1.0199, 1.0258, 1.0292]])
Expand Down Expand Up @@ -85,15 +86,24 @@ def simulate(
Args:
n_paths (int, default=1): The number of paths to simulate.
time_horizon (float, default=20/250): The period of time to simulate the price.
time_horizon (float, default=20/250): The period of time to simulate
the price.
init_state (tuple, optional): The initial state of the instrument.
`init_state` should be a 1-tuple `(spot,)`
where spot is the initial spot price.
If `None` (default), the default value `(1.0,)` is chosen.
**kwargs: Other parameters passed to `self.underlier.simulate()`.
Examples:
>>> _ = torch.manual_seed(42)
>>> stock = BrownianStock()
>>> stock.simulate(n_paths=2, time_horizon=5 / 250, init_state=(2.0,))
>>> stock.spot
tensor([[2.0000, 2.0031, 2.0089, 2.0146, 1.9860],
[2.0000, 2.0565, 2.0398, 2.0516, 2.0584]])
"""
if init_state is None:
# Default setting
# Default value
init_state = (1.0,)

self.spot = generate_geometric_brownian(
Expand Down
Loading

0 comments on commit 0518c9a

Please sign in to comment.