Skip to content

Commit

Permalink
Release/0.18.0 (#531)
Browse files Browse the repository at this point in the history
* ENH: Add `pfhedge.__version__` support (#514)

* ENH: Add Black-Scholes formulas as functional (#489) (#506)

* ENH: Add `end_index` to forward start payoff functional (#518)

* ENH: Add `clauses`, `named_clauses` to derivative (#520)

* ENH: implicit args for Black-Scholes modules (#516)

* ENH: Add bilinear interpolation function `bilerp` (close #523) (#527)

* ENH: Add `.float16()`, `.float32(), `.float64()` to Instrument (#524)

* BUG: Stop assigning arbitrary strike to autogreek.delta (#517)

* DOC: Update functional documentations (#508)

* DOC: Add note on discrete/continuous monitoring (#513)

* DOC: Add note on adding clause (#515)

* DOC: Elaborate documentation on payoff (#519)

* MAINT: Refactor BlackScholes module using factory (close #509) (#510)

* MAINT: Miscellaneous refactoring (#507) (#521) (#525)

* CHORE: Run Publish action on release (#504)

* Bumping version from 0.17.0 to 0.18.0 (#532)

Co-authored-by: Masanori HIRANO <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Mar 9, 2022
1 parent 5c71d6f commit 0460355
Show file tree
Hide file tree
Showing 43 changed files with 3,481 additions and 465 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ jobs:
- name: Bump version
run: git branch --show-current | sed 's|release/||' | xargs poetry version | { printf '::set-output name=PR_TITLE::'; cat; }
id: bump

- name: Bump pfhedge.__version__
run: git branch --show-current | sed 's|release/||' | xargs -I {} echo '__version__ = "{}"' > pfhedge/version.py

- name: Create pull request
uses: peter-evans/create-pull-request@v3
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Publish

on:
push:
branches: main
release:
types: [published]
workflow_dispatch:

jobs:
Expand Down
55 changes: 46 additions & 9 deletions docs/source/nn.functional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ Payoff Functions
european_binary_payoff
european_forward_start_payoff

Nonlinear activation functions
------------------------------

.. autosummary::
:nosignatures:
:toctree: generated

leaky_clamp
clamp

Criterion Functions
-------------------

Expand All @@ -32,21 +42,48 @@ Criterion Functions
expected_shortfall
value_at_risk

Black-Scholes formulas
----------------------

.. autosummary::
:nosignatures:
:toctree: generated

bs_european_price
bs_european_delta
bs_european_gamma
bs_european_vega
bs_european_theta
bs_american_binary_price
bs_american_binary_delta
bs_american_binary_gamma
bs_american_binary_vega
bs_american_binary_theta
bs_european_binary_price
bs_european_binary_delta
bs_european_binary_gamma
bs_european_binary_vega
bs_european_binary_theta
bs_lookback_price
bs_lookback_delta
bs_lookback_gamma
bs_lookback_vega
bs_lookback_theta

Other Functions
-----------------
---------------

.. autosummary::
:nosignatures:
:toctree: generated

leaky_clamp
clamp
topp
realized_variance
realized_volatility
terminal_value
ncdf
npdf
bilerp
d1
d2
ncdf
npdf
realized_variance
realized_volatility
svi_variance
terminal_value
topp
69 changes: 69 additions & 0 deletions docs/source/notes/adding_clause.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Adding clause to derivative
===========================

One can customize a derivative by registering additional clauses;
see :func:`pfhedge.instruments.BaseDerivative.add_clause`.

Let us see how to add clauses to derivatives by taking European capped call option as an example.
This option is a variant of a European option, which is given by

.. code:: python
>>> from pfhedge.instruments import BrownianStock
>>> from pfhedge.instruments import EuropeanOption
...
>>> strike = 1.0
>>> maturity = 1.0
>>> stock = BrownianStock()
>>> european = EuropeanOption(stock, strike=strike, maturity=maturity)
The capped call associates ''a barrier clause'';
if the underlier's spot reaches the barrier price :math:`B`,
being greater than the strike :math:`K` and the spot at inception,
the option immediately expires and pays off its intrinsic value at that moment :math:`B - K`.

This clause can be registered to a derivative as follows:

.. code:: python
>>> def cap_clause(derivative, payoff):
... barrier = 1.4
... max_spot = derivative.ul().spot.max(-1).values
... capped_payoff = torch.full_like(payoff, barrier - strike)
... return torch.where(max_spot < barrier, payoff, capped_payoff)
...
>>> capped_european = EuropeanOption(stock, strike=strike, maturity=maturity)
>>> capped_european.add_clause("cap_clause", cap_clause)
The method ``add_clause`` adds the clause and its name to the derivative.
Here the function ``cap_caluse`` represents the clause to modify the payoff depending on the state of the derivative.
The clause function should have the signature ``clause(derivative, payoff) -> modified payoff``.

The payoff would be capped as intended:

.. code:: python
>>> n_paths = 100000
>>> capped_european.simulate(n_paths=n_paths)
>>> european.payoff().max()
>>> # 1.2...
>>> capped_european.payoff().max()
>>> # 0.4
The price of the capped European call option can be evaluated by using a European option as a control variates.

.. code:: python
>>> from math import sqrt
...
>>> payoff_european = european.payoff()
>>> payoff_capped_european = capped_european.payoff()
>>> bs_price = BlackScholes(european).price(0.0, european.maturity, stock.sigma).item()
>>> price = bs_price + (payoff_capped_european - payoff_european).mean().item()
>>> error = (payoff_capped_european - payoff_european).std().item() / sqrt(n_paths)
>>> bs_price
>>> # 0.07967...
>>> price
>>> # 0.07903...
>>> error
>>> # 0.00012...
57 changes: 57 additions & 0 deletions examples/example_adding_clause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import sys

sys.path.append("..")

from math import sqrt

import torch

from pfhedge.instruments import BrownianStock
from pfhedge.instruments import EuropeanOption
from pfhedge.nn import BlackScholes


def main():
torch.manual_seed(42)

strike = 1.0
maturity = 1.0
stock = BrownianStock()
european = EuropeanOption(stock, strike=strike, maturity=maturity)

def cap_clause(derivative, payoff):
barrier = 1.4
max_spot = derivative.ul().spot.max(-1).values
capped_payoff = torch.full_like(payoff, barrier - strike)
return torch.where(max_spot < barrier, payoff, capped_payoff)

capped_european = EuropeanOption(stock, strike=strike, maturity=maturity)
capped_european.add_clause("cap_clause", cap_clause)

n_paths = 100000
capped_european.simulate(n_paths=n_paths)

payoff_european = european.payoff()
payoff_capped_european = capped_european.payoff()

max_spot = payoff_european.max().item()
capped_max_spot = payoff_capped_european.max().item()
print("Max payoff of vanilla European:", max_spot)
print("Max payoff of capped European:", capped_max_spot)

# Price using control variates
bs_price = BlackScholes(european).price(0.0, european.maturity, stock.sigma).item()
value0 = payoff_capped_european.mean().item()
value1 = bs_price + (payoff_capped_european - payoff_european).mean().item()
error0 = payoff_capped_european.std().item() / sqrt(n_paths)
error1 = (payoff_capped_european - payoff_european).std().item() / sqrt(n_paths)

print("BS price of vanilla European:", bs_price)
print("Price of capped European without control variates:", value0)
print("Price of capped European with control variates:", value1)
print("Error of capped European without control variates:", error0)
print("Error of capped European with control variates:", error1)


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions pfhedge/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Users can import Hedger either as `from pfhedge import Hedger` or
# `from pfhedge.nn import Hedger`
from pfhedge.nn import Hedger

from .version import __version__
3 changes: 2 additions & 1 deletion pfhedge/_utils/bisect.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Any
from typing import Callable
from typing import Union

Expand Down Expand Up @@ -86,7 +87,7 @@ def find_implied_volatility(
upper: float = 1.000,
precision: float = 1e-6,
max_iter: int = 100,
**params,
**params: Any,
) -> Tensor:
"""Find implied volatility by binary search.
Expand Down
3 changes: 2 additions & 1 deletion pfhedge/_utils/operations.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from typing import Any
from typing import Callable

import torch
from torch import Tensor


def ensemble_mean(
function: Callable[..., Tensor], n_times: int = 1, *args, **kwargs
function: Callable[..., Tensor], n_times: int = 1, *args: Any, **kwargs: Any
) -> Tensor:
"""Compute ensemble mean from function.
Expand Down
10 changes: 7 additions & 3 deletions pfhedge/_utils/parse.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from numbers import Real
from typing import Any
from typing import Optional
from typing import Union

Expand All @@ -16,7 +17,7 @@ def parse_spot(
strike: Optional[Tensor] = None,
moneyness: Optional[Tensor] = None,
log_moneyness: Optional[Tensor] = None,
**kwargs
**kwargs: Any
) -> Tensor:
spot = _as_optional_tensor(spot)
strike = _as_optional_tensor(strike)
Expand All @@ -34,7 +35,10 @@ def parse_spot(


def parse_volatility(
*, volatility: Optional[Tensor] = None, variance: Optional[Tensor] = None, **kwargs
*,
volatility: Optional[Tensor] = None,
variance: Optional[Tensor] = None,
**kwargs: Any
) -> Tensor:
if volatility is not None:
return volatility
Expand All @@ -45,7 +49,7 @@ def parse_volatility(


def parse_time_to_maturity(
*, time_to_maturity: Optional[Tensor] = None, **kwargs
*, time_to_maturity: Optional[Tensor] = None, **kwargs: Any
) -> Tensor:
if time_to_maturity is not None:
return time_to_maturity
Expand Down
5 changes: 3 additions & 2 deletions pfhedge/_utils/testing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Any
from typing import Callable

import torch
Expand Down Expand Up @@ -62,7 +63,7 @@ def assert_convex(


def assert_cash_invariant(
fn: Callable[[Tensor], Tensor], x: Tensor, c: float, **kwargs
fn: Callable[[Tensor], Tensor], x: Tensor, c: float, **kwargs: Any
) -> None:
"""Assert cash invariance.
Expand All @@ -78,7 +79,7 @@ def assert_cash_invariant(


def assert_cash_equivalent(
fn: Callable[[Tensor], Tensor], x: Tensor, c: float, **kwargs
fn: Callable[[Tensor], Tensor], x: Tensor, c: float, **kwargs: Any
) -> None:
"""Assert ``c`` is the cash equivalent of ``x``.
``fn(x) = fn(torch.full_like(x, c))``
Expand Down
Loading

0 comments on commit 0460355

Please sign in to comment.