Skip to content

Commit

Permalink
Release/0.8.1 (#205)
Browse files Browse the repository at this point in the history
* ENH: Add `volatility` to `HestonStock` (#201)

* Change default value of sigma in Heston and CIR (close #203) (#204) (Thank you, @masanorihirano !)

* DOC: Document buffers of primary instruments (#200)

Co-authored-by: Masanori HIRANO <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: GitHub Actions <[email protected]>
  • Loading branch information
4 people committed Aug 16, 2021
1 parent 1904018 commit f84dcff
Show file tree
Hide file tree
Showing 28 changed files with 218 additions and 132 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ doc:

.PHONY: cov-html
cov-html:
@pytest --cov=$(PROJECT_NAME) --cov-report=html
@poetry run pytest --cov=$(PROJECT_NAME) --cov-report=html
14 changes: 8 additions & 6 deletions pfhedge/features/_getter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import Dict
from typing import Union

from ._base import Feature
from .features import Empty
from .features import ExpiryTime
Expand All @@ -22,7 +25,7 @@
]


def get_feature(feature):
def get_feature(feature: Union[str, Feature]) -> Feature:
"""Get feature from name.
Args:
Expand All @@ -31,7 +34,7 @@ def get_feature(feature):
Returns:
Feature
"""
dict_features = {str(f): f for f in FEATURES}
dict_features: Dict[str, Feature] = {str(f): f for f in FEATURES}

if isinstance(feature, str):
if feature not in dict_features:
Expand All @@ -40,8 +43,7 @@ def get_feature(feature):
"Use sorted(pfhedge.features.FEATURES) to get valid options."
)
feature = dict_features[feature]
else:
# If `feature` is Feature object, pass it through.
if not isinstance(feature, Feature):
raise TypeError(f"{feature} is not an instance of 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
56 changes: 30 additions & 26 deletions pfhedge/features/features.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from typing import List

import torch
from torch import Tensor
from torch.nn import Module

from ._base import Feature
from .functional import barrier
Expand All @@ -17,14 +21,14 @@ class Moneyness(Feature):
log (bool, default=False): If `True`, represents log moneyness.
"""

def __init__(self, log=False):
def __init__(self, log: bool = False) -> None:
super().__init__()
self.log = log

def __str__(self):
def __str__(self) -> str:
return "log_moneyness" if self.log else "moneyness"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
if self.log:
return self.derivative.log_moneyness(i).unsqueeze(-1)
else:
Expand All @@ -34,37 +38,37 @@ def __getitem__(self, i):
class LogMoneyness(Moneyness):
"""Log moneyness of the underlying instrument of the derivative."""

def __init__(self):
def __init__(self) -> None:
super().__init__(log=True)


class ExpiryTime(Feature):
"""Remaining time to the maturity of the derivative."""

def __str__(self):
def __str__(self) -> str:
return "expiry_time"

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


class Volatility(Feature):
"""Volatility of the underlier of the derivative."""

def __str__(self):
def __str__(self) -> str:
return "volatility"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return volatility(i, derivative=self.derivative)


class PrevHedge(Feature):
"""Previous holding of underlier."""

def __str__(self):
def __str__(self) -> str:
return "prev_hedge"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return prev_hedge(i, derivative=self.derivative, hedger=self.hedger)


Expand All @@ -80,15 +84,15 @@ class Barrier(Feature):
If `False`, signifies whether the price has exceeded the barrier downward.
"""

def __init__(self, threshold, up=True):
def __init__(self, threshold: float, up: bool = True) -> None:
super().__init__()
self.threshold = threshold
self.up = up

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

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return barrier(
i, derivative=self.derivative, threshold=self.threshold, up=self.up
)
Expand All @@ -97,20 +101,20 @@ def __getitem__(self, i):
class Zeros(Feature):
"""A feature of which value is always zero."""

def __str__(self):
def __str__(self) -> str:
return "zeros"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return zeros(i, derivative=self.derivative)


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

def __str__(self):
def __str__(self) -> str:
return "empty"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return empty(i, derivative=self.derivative)


Expand All @@ -121,14 +125,14 @@ class MaxMoneyness(Feature):
log (bool, default=False): If `True`, represents log moneyness.
"""

def __init__(self, log=False):
def __init__(self, log: bool = False) -> None:
super().__init__()
self.log = log

def __str__(self):
def __str__(self) -> str:
return "max_log_moneyness" if self.log else "max_moneyness"

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
if self.log:
return max_log_moneyness(i, derivative=self.derivative)
else:
Expand All @@ -138,11 +142,11 @@ def __getitem__(self, i):
class MaxLogMoneyness(MaxMoneyness):
"""Cumulative maximum of log Moneyness."""

def __init__(self):
def __init__(self) -> None:
super().__init__(log=True)


class ModuleOutput(Feature, torch.nn.Module):
class ModuleOutput(Feature, Module):
"""The feature computed as an output of a `torch.nn.Module`.
Args:
Expand Down Expand Up @@ -187,19 +191,19 @@ class ModuleOutput(Feature, torch.nn.Module):
[...]])
"""

def __init__(self, module, inputs):
def __init__(self, module: Module, inputs: List[Feature]) -> None:
super().__init__()

self.module = module
self.inputs = inputs

def extra_repr(self):
def extra_repr(self) -> str:
return "inputs=" + str([str(f) for f in self.inputs])

def forward(self, input):
def forward(self, input: Tensor) -> Tensor:
return self.module(input)

def __getitem__(self, i):
def __getitem__(self, i: int) -> Tensor:
return self(torch.cat([f[i] for f in self.inputs], 1))

def of(self, derivative=None, hedger=None):
Expand Down
21 changes: 13 additions & 8 deletions pfhedge/features/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
from torch import Tensor


def volatility(i, derivative, hedger=None) -> Tensor:
def volatility(i: int, derivative, hedger=None) -> Tensor:
value = derivative.ul().volatility
return torch.full_like(derivative.ul().spot[:, :1], value)
if isinstance(value, torch.Tensor):
if not derivative.ul().spot.size() == value.size():
raise ValueError("spot tensor and volatility tensor are not the same size")
return value[:, i : i + 1]
else:
return torch.full_like(derivative.ul().spot[:, :1], value)


def prev_hedge(i, derivative, hedger) -> Tensor:
def prev_hedge(i: int, derivative, hedger) -> Tensor:
if hasattr(hedger, "prev_output"):
return hedger.prev_output
else:
Expand All @@ -16,7 +21,7 @@ def prev_hedge(i, derivative, hedger) -> Tensor:


def barrier(
i, derivative, hedger=None, threshold: float = 1.0, up: bool = True
i: int, derivative, hedger=None, threshold: float = 1.0, up: bool = True
) -> Tensor:
if up:
# shape: (N, i)
Expand All @@ -26,19 +31,19 @@ def barrier(
return touch_threshold.any(dim=-1, keepdim=True).to(derivative.ul().spot)


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


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


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


def max_log_moneyness(i, derivative, hedger=None) -> Tensor:
def max_log_moneyness(i: int, derivative, hedger=None) -> Tensor:
return max_moneyness(i, derivative=derivative).log()
3 changes: 2 additions & 1 deletion pfhedge/instruments/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from abc import ABC
from abc import abstractmethod
from typing import List
from typing import Optional
from typing import TypeVar
from typing import no_type_check
Expand Down Expand Up @@ -92,7 +93,7 @@ def bfloat16(self: T) -> T:
return self.to(torch.bfloat16)

@property
def dinfo(self) -> list:
def dinfo(self) -> List[str]:
"""Returns list of strings that tell `dtype` and `device` of `self`.
Intended to be used in `__repr__`.
Expand Down
11 changes: 7 additions & 4 deletions pfhedge/instruments/derivative/american_binary.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Optional

import torch
from torch import Tensor

from pfhedge._utils.doc import set_attr_and_docstring
from pfhedge._utils.doc import set_docstring

from ...nn.functional import american_binary_payoff
from ..primary.base import Primary
from .base import Derivative
from .base import OptionMixin

Expand Down Expand Up @@ -83,13 +86,13 @@ class AmericanBinaryOption(Derivative, OptionMixin):

def __init__(
self,
underlier,
underlier: Primary,
call: bool = True,
strike: float = 1.0,
maturity: float = 20 / 250,
dtype: torch.dtype = None,
device: torch.device = None,
):
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
) -> None:
super().__init__()
self.underlier = underlier
self.call = call
Expand Down
6 changes: 3 additions & 3 deletions pfhedge/instruments/derivative/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class OptionMixin:
strike: float
maturity: float

def moneyness(self, time_step: int = None) -> Tensor:
def moneyness(self, time_step: Optional[int] = None) -> Tensor:
"""Returns the moneyness of self.
Args:
Expand All @@ -112,7 +112,7 @@ def moneyness(self, time_step: int = None) -> Tensor:
spot = spot[..., time_step]
return spot / self.strike

def log_moneyness(self, time_step: int = None) -> Tensor:
def log_moneyness(self, time_step: Optional[int] = None) -> Tensor:
"""Returns the log moneyness of self.
Args:
Expand All @@ -130,7 +130,7 @@ def log_moneyness(self, time_step: int = None) -> Tensor:
"""
return self.moneyness(time_step=time_step).log()

def time_to_maturity(self, time_step: int = None) -> Tensor:
def time_to_maturity(self, time_step: Optional[int] = None) -> Tensor:
"""Returns the time to maturity of self.
Args:
Expand Down
11 changes: 7 additions & 4 deletions pfhedge/instruments/derivative/european.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Optional

import torch
from torch import Tensor

from pfhedge._utils.doc import set_attr_and_docstring
from pfhedge._utils.doc import set_docstring

from ...nn.functional import european_payoff
from ..primary.base import Primary
from .base import Derivative
from .base import OptionMixin

Expand Down Expand Up @@ -74,13 +77,13 @@ class EuropeanOption(Derivative, OptionMixin):

def __init__(
self,
underlier,
underlier: Primary,
call: bool = True,
strike: float = 1.0,
maturity: float = 20 / 250,
dtype: torch.dtype = None,
device: torch.device = None,
):
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
) -> None:
super().__init__()
self.underlier = underlier
self.call = call
Expand Down
Loading

0 comments on commit f84dcff

Please sign in to comment.