Skip to content

Commit

Permalink
Release/0.14.0 (#382)
Browse files Browse the repository at this point in the history
* ENH: Add `entropic_risk_measure` to `nn.functional` (close #352) (#372)

* ENH: Add `value_at_risk` to `nn.functional` (#371)

* MAINT: Add typing (#378)

* MAINT: Drop Python 3.6 (close #356) (#357)

  * Python 3.6 is no longer supported. Please update to >=3.7.

* CHORE: Support PyTorch 1.10 (#377)

* CHORE: Update README.md (#375)

* CHORE: Update pytest-cov requirement from ^2.8.1 to ^3.0.0 (#367)

* CHORE: Update Makefile (#381)

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 Oct 31, 2021
1 parent c5fd733 commit 74ead55
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 41 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

env:
PROJECT_NAME: pfhedge
PYTHON_VERSION: '3.9'

on:
push:
Expand All @@ -28,7 +29,7 @@ jobs:

strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
python-version: ['3.7', '3.8', '3.9']

steps:

Expand Down Expand Up @@ -64,6 +65,8 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}

- run: pip install poetry && poetry install

Expand All @@ -83,6 +86,8 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}

- uses: psf/[email protected]
with:
Expand All @@ -107,6 +112,8 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}

- uses: psf/[email protected]
with:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/doc.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: Doc

env:
PROJECT_NAME: pfhedge
PYTHON_VERSION: '3.9'

on:
push:
branches:
Expand All @@ -26,6 +30,8 @@ jobs:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}

- run: pip install poetry && poetry install

Expand Down
17 changes: 9 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PROJECT_NAME := pfhedge
RUN := poetry run

.PHONY: check
check: test lint mypy
Expand All @@ -12,41 +13,41 @@ test: doctest pytest

.PHONY: doctest
doctest:
@poetry run pytest --doctest-modules $(PROJECT_NAME)
$(RUN) pytest --doctest-modules $(PROJECT_NAME)

.PHONY: pytest
pytest:
@poetry run pytest --doctest-modules tests
$(RUN) pytest --doctest-modules tests

.PHONY: test-cov
test-cov:
@poetry run pytest --cov=$(PROJECT_NAME) --cov-report=html
$(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=html

.PHONY: lint
lint: lint-black lint-isort

.PHONY: lint-black
lint-black:
@poetry run black --check --diff --quiet --skip-magic-trailing-comma .
$(RUN) black --check --diff --quiet --skip-magic-trailing-comma .

.PHONY: lint-isort
lint-isort:
@poetry run isort --check --force-single-line-imports --quiet .
$(RUN) isort --check --force-single-line-imports --quiet .

.PHONY: mypy
mypy:
@poetry run mypy $(PROJECT_NAME)
$(RUN) mypy $(PROJECT_NAME)

.PHONY: format
format: format-black format-isort

.PHONY: format-black
format-black:
@poetry run black --quiet --skip-magic-trailing-comma .
$(RUN) black --quiet --skip-magic-trailing-comma .

.PHONY: format-isort
format-isort:
@poetry run isort --force-single-line-imports --quiet .
$(RUN) isort --force-single-line-imports --quiet .

.PHONY: doc
doc:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ We hope PFHedge accelerates the research and development of Deep Hedging.
## Install

```sh
$ pip install pfhedge
pip install pfhedge
```

## How to Use
Expand Down
1 change: 0 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Install:

notes/*


.. toctree::
:caption: Development
:hidden:
Expand Down
6 changes: 4 additions & 2 deletions docs/source/nn.functional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ Payoff Functions
.. autofunction:: american_binary_payoff
.. autofunction:: european_binary_payoff

Utility Functions
-----------------
Criterion Functions
-------------------

.. autofunction:: exp_utility
.. autofunction:: isoelastic_utility
.. autofunction:: entropic_risk_measure
.. autofunction:: expected_shortfall
.. autofunction:: value_at_risk

Other Functions
-----------------
Expand Down
4 changes: 2 additions & 2 deletions pfhedge/_utils/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def save_prev_output(
>>> hook = m.register_forward_hook(save_prev_output)
>>> input = torch.randn(1, 3)
>>> m(input)
tensor([[-1.1647, 0.0244]], grad_fn=<AddmmBackward>)
tensor([[-1.1647, 0.0244]], ...)
>>> m.prev_output
tensor([[-1.1647, 0.0244]], grad_fn=<AddmmBackward>)
tensor([[-1.1647, 0.0244]], ...)
"""
module.register_buffer("prev_output", output, persistent=False)
1 change: 0 additions & 1 deletion pfhedge/_utils/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def ensemble_mean(
torch.Tensor
Examples:
>>> function = lambda: torch.tensor([1.0, 2.0])
>>> ensemble_mean(function, 5)
tensor([1., 2.])
Expand Down
2 changes: 1 addition & 1 deletion pfhedge/features/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Feature(ABC):
derivative: Derivative
hedger: Optional[Module]

def __init__(self):
def __init__(self) -> None:
self.register_hedger(None)

@abstractmethod
Expand Down
6 changes: 3 additions & 3 deletions pfhedge/features/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class FeatureList(Feature):
def __init__(self, features: List[Union[str, Feature]]):
self.features = list(map(get_feature, features))

def __len__(self):
def __len__(self) -> int:
return len(self.features)

def get(self, time_step: Optional[int]) -> Tensor:
# Return size: (N, T, F)
return torch.cat([f.get(time_step) for f in self.features], dim=-1)

def __repr__(self):
def __repr__(self) -> str:
return str(list(map(str, self.features)))

def of(self: T, derivative: Derivative, hedger: Optional[Module] = None) -> T:
Expand Down Expand Up @@ -125,5 +125,5 @@ def of(self, derivative=None, hedger=None):
self.inputs = self.inputs.of(derivative, hedger)
return self

def is_state_dependent(self):
def is_state_dependent(self) -> bool:
return self.inputs.is_state_dependent()
2 changes: 1 addition & 1 deletion pfhedge/instruments/derivative/american_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def __init__(
"Specify them in the constructor of the underlier instead."
)

def extra_repr(self):
def extra_repr(self) -> str:
params = []
if not self.call:
params.append("call=" + str(self.call))
Expand Down
6 changes: 4 additions & 2 deletions pfhedge/instruments/derivative/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Derivative(Instrument):
pricer: Optional[Callable[[Any], Tensor]]
_clauses: Dict[str, Callable[["Derivative", Tensor], Tensor]]

def __init__(self):
def __init__(self) -> None:
super().__init__()
self.pricer = None
self.cost = 0.0
Expand Down Expand Up @@ -252,7 +252,9 @@ def time_to_maturity(self, time_step: Optional[int] = None) -> Tensor:
t = torch.tensor([[time]]).to(self.underlier.spot) * self.underlier.dt
return t.expand(n_paths, -1)

def max_moneyness(self, time_step: Optional[int] = None, log=False) -> Tensor:
def max_moneyness(
self, time_step: Optional[int] = None, log: bool = False
) -> Tensor:
"""Returns the cumulative maximum of the moneyness.
Args:
Expand Down
2 changes: 1 addition & 1 deletion pfhedge/instruments/derivative/european.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def __init__(
"Specify them in the constructor of the underlier instead."
)

def extra_repr(self):
def extra_repr(self) -> str:
params = []
if not self.call:
params.append("call=" + str(self.call))
Expand Down
2 changes: 1 addition & 1 deletion pfhedge/instruments/derivative/variance_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def __init__(
"Specify them in the constructor of the underlier instead."
)

def extra_repr(self):
def extra_repr(self) -> str:
return ", ".join(
(
"strike=" + _format_float(self.strike),
Expand Down
65 changes: 59 additions & 6 deletions pfhedge/nn/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ def isoelastic_utility(input: Tensor, a: float) -> Tensor:
return input.pow(1.0 - a)


def entropic_risk_measure(input: Tensor, a: float = 1.0) -> Tensor:
"""Returns the entropic risk measure.
See :class:`pfhedge.nn.EntropicRiskMeasure` for details.
"""
return (-exp_utility(input, a=a).mean(0)).log() / a


def topp(input: Tensor, p: float, dim: Optional[int] = None, largest: bool = True):
"""Returns the largest :math:`p * N` elements of the given input tensor,
where :math:`N` stands for the total number of elements in the input tensor.
Expand All @@ -166,7 +174,7 @@ def topp(input: Tensor, p: float, dim: Optional[int] = None, largest: bool = Tru
Args:
input (torch.Tensor): The input tensor.
p (float): Quantile level.
p (float): The quantile level.
dim (int, optional): The dimension to sort along.
largest (bool, default=True): Controls whether to return largest or smallest
elements.
Expand All @@ -175,7 +183,6 @@ def topp(input: Tensor, p: float, dim: Optional[int] = None, largest: bool = Tru
torch.Tensor
Examples:
>>> from pfhedge.nn.functional import topp
>>>
>>> input = torch.arange(1.0, 6.0)
Expand All @@ -197,15 +204,15 @@ def expected_shortfall(input: Tensor, p: float, dim: Optional[int] = None) -> Te
Args:
input (torch.Tensor): The input tensor.
p (float): Quantile level.
p (float): The quantile level.
dim (int, optional): The dimension to sort along.
Examples:
>>> from pfhedge.nn.functional import expected_shortfall
>>>
>>> input = -torch.arange(1.0, 10.0)
>>> input = -torch.arange(10.0)
>>> input
tensor([-1., -2., -3., -4., -5., -6., -7., -8., -9.])
tensor([-0., -1., -2., -3., -4., -5., -6., -7., -8., -9.])
>>> expected_shortfall(input, 0.3)
tensor(8.)
Expand All @@ -218,6 +225,52 @@ def expected_shortfall(input: Tensor, p: float, dim: Optional[int] = None) -> Te
return -topp(input, p=p, largest=False, dim=dim).values.mean(dim=dim)


def _min_values(input: Tensor, dim: Optional[int] = None) -> Tensor:
return input.min() if dim is None else input.min(dim=dim).values


def _max_values(input: Tensor, dim: Optional[int] = None) -> Tensor:
return input.max() if dim is None else input.max(dim=dim).values


def value_at_risk(input: Tensor, p: float, dim: Optional[int] = None) -> Tensor:
"""Returns the value at risk of the given input tensor.
Note:
If :math:`p \leq 1 / N`` with :math:`N` being the number of elements to sort,
returns the smallest element in the tensor.
If :math:`p > 1 - 1 / N``, returns the largest element in the tensor.
Args:
input (torch.Tensor): The input tensor.
p (float): The quantile level.
dim (int, optional): The dimension to sort along.
Examples:
>>> from pfhedge.nn.functional import value_at_risk
>>>
>>> input = -torch.arange(10.0)
>>> input
tensor([-0., -1., -2., -3., -4., -5., -6., -7., -8., -9.])
>>> value_at_risk(input, 0.3)
tensor(-7.)
Returns:
torch.Tensor
"""
n = input.numel() if dim is None else input.size(dim)

if p <= 1 / n:
output = _min_values(input, dim=dim)
elif p > 1 - 1 / n:
output = _max_values(input, dim=dim)
else:
q = (p - (1 / n)) / (1 - (1 / n))
output = input.quantile(q, dim=dim)

return output


def leaky_clamp(
input: Tensor,
min: Optional[Tensor] = None,
Expand Down Expand Up @@ -327,7 +380,7 @@ def terminal_value(
cost: float = 0.0,
payoff: Optional[Tensor] = None,
deduct_first_cost: bool = True,
):
) -> Tensor:
r"""Returns the terminal portfolio value.
The terminal value of a hedger's portfolio is given by
Expand Down
3 changes: 2 additions & 1 deletion pfhedge/nn/modules/loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pfhedge._utils.bisect import bisect
from pfhedge._utils.str import _format_float

from ..functional import entropic_risk_measure
from ..functional import exp_utility
from ..functional import expected_shortfall
from ..functional import isoelastic_utility
Expand Down Expand Up @@ -112,7 +113,7 @@ def extra_repr(self) -> str:
return "a=" + _format_float(self.a) if self.a != 1 else ""

def forward(self, input: Tensor) -> Tensor:
return (-exp_utility(input, a=self.a).mean(0)).log() / self.a
return entropic_risk_measure(input, a=self.a)

def cash(self, input: Tensor) -> Tensor:
return -self(input)
Expand Down
2 changes: 1 addition & 1 deletion pfhedge/stochastic/heston.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class HestonTuple(namedtuple("HestonTuple", ["spot", "variance"])):

__module__ = "pfhedge.stochastic"

def __repr__(self):
def __repr__(self) -> str:
items_str_list = []
for field, tensor in self._asdict().items():
items_str_list.append(field + "=\n" + str(tensor))
Expand Down
Loading

0 comments on commit 74ead55

Please sign in to comment.