Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add norm_cdf for Dual and FX options #115

Merged
merged 47 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5d36af9
ENH: dual norm cdf
attack68 Dec 14, 2023
e0d9c7e
ENH: dual norm cdf
attack68 Dec 15, 2023
f99a23a
ENH: dual norm cdf
attack68 Dec 15, 2023
8eb937c
ENH: fx option
attack68 Dec 16, 2023
7e3f11b
ENH: implied vol
attack68 Dec 16, 2023
b5d3c2e
ENH: options tests
attack68 Dec 17, 2023
366ef85
ENH: expose maybe_local to some NPVs
attack68 Dec 17, 2023
0e57363
ENH: expose maybe_local to some NPVs
attack68 Dec 17, 2023
e17b7df
ENH: expose maybe_local to some NPVs
attack68 Dec 17, 2023
22f9fcd
ENH: expose maybe_local to some NPVs
attack68 Dec 18, 2023
115d4f9
ENH: build option instrument
attack68 Dec 19, 2023
0fd9ba9
Merge branch 'main' into dual_norm_cdf
attack68 Dec 19, 2023
aae9934
change dual2 inv norm and fxoption
attack68 Dec 21, 2023
f84b2ea
change dual2 inv norm and fxoption
attack68 Dec 21, 2023
9e858ad
change dual2 inv norm and fxoption
attack68 Dec 22, 2023
14ea5fe
ENH: dual norm cdf
attack68 Dec 22, 2023
05281e8
ENH: dual norm cdf
attack68 Dec 22, 2023
9998a2a
ENH: dual norm cdf
attack68 Dec 22, 2023
e79205f
change dual2 inv norm and fxoption
attack68 Dec 22, 2023
ecf66ea
change dual2 inv norm and fxoption
attack68 Dec 22, 2023
524aaf7
change dual2 inv norm and fxoption
attack68 Dec 23, 2023
4ec9d3b
change dual2 inv norm and fxoption
attack68 Dec 23, 2023
68e35e7
change dual2 inv norm and fxoption
attack68 Dec 23, 2023
ef00bd2
ADD pairs_settlement attribute to FXRates and FXForwards
attack68 Dec 24, 2023
259fbc4
Relate option premiums to defined spot dates
attack68 Dec 24, 2023
042a8bb
Merge branch 'main' into dual_norm_cdf
attack68 Dec 24, 2023
889acfc
docs risk reversal
attack68 Dec 30, 2023
d60fb19
docs risk reversal
attack68 Dec 30, 2023
753775f
docs risk reversal
attack68 Dec 30, 2023
be17057
TST: risk reversal plot
attack68 Jan 15, 2024
d622b6c
Merge remote-tracking branch 'origin/main' into dual_norm_cdf
attack68 Jan 15, 2024
a9bf598
Merge branch 'main' into dual_norm_cdf
attack68 Feb 21, 2024
a282e6c
Merge branch 'main' into dual_norm_cdf
attack68 Mar 2, 2024
585d670
Merge remote-tracking branch 'origin/main' into dual_norm_cdf
attack68 Mar 28, 2024
893ea67
clean up files
attack68 Mar 28, 2024
5f211dc
Python spline equality
attack68 Mar 28, 2024
920675d
DualPy: norm inv and norm dual calcs
attack68 Mar 29, 2024
4ca1cd5
Rust dual1 cdf
attack68 Mar 29, 2024
9b3374c
Rust dual2 cdf
attack68 Mar 29, 2024
e8db092
Rust dual2 cdf
attack68 Mar 29, 2024
ac55439
doc for FX options
attack68 Mar 31, 2024
6eb2e43
premium adjusted delta for FX options
attack68 Apr 2, 2024
fcea649
premium adjusted delta for FX options
attack68 Apr 4, 2024
0fa5e5f
docs
attack68 Apr 4, 2024
c1c6346
Docs and Tests
attack68 Apr 5, 2024
c9ee4f4
Imports
attack68 Apr 5, 2024
915ef83
Imports
attack68 Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ num-traits = "0.2.15"
auto_ops = "0.3.0"
numpy = "0.20.0"
itertools = "0.12.1"
statrs = "0.16"

# --- This section should be live in development to use `cargo test --lib --no-default-features`
[dependencies.pyo3]
Expand Down
Binary file added docs/source/_static/fx_opt_bbg_eurusd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/source/d_instruments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ Multi-Currency Derivatives
Link to the section on :ref:`multi-currency derivatives<multicurrency-doc>`. This allows
*FXSwaps*, *Cross-Currency Swaps* and *FX Exchanges*.

Volatility
------------
Link to the section on :ref:`FX volatility products<volatility-doc>`. This allows
*FXCall*, *FXPut* and *FXRiskReversal* instruments.

Utilities and Instrument Combinations
-------------------------------------

Expand All @@ -49,4 +54,5 @@ allows things like *Spread trades*, *Butterflies*, *Portfolios* and a *Value* fo
e_securities.rst
e_singlecurrency.rst
e_multicurrency.rst
e_volatility.rst
e_combinations.rst
329 changes: 329 additions & 0 deletions docs/source/e_volatility.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
.. _volatility-doc:

.. ipython:: python
:suppress:

from rateslib.instruments import *
from datetime import datetime as dt

****************************
FX Volatility
****************************

Interbank standard conventions for quoting FX volatility products are quite varied.
None-the-less, *rateslib* provides the most common definitions and products, all priced using
the **Black-76** model.

Currently, in v1.2.x, there is no ability to build a volatility surface, but this is planned for
short term implementation. Volatility inputs to pricing formulae currently rely on an explicit
volatility value.

The following *Instruments* are currently available.

.. inheritance-diagram:: rateslib.instruments.FXCall rateslib.instruments.FXPut rateslib.instruments.FXRiskReversal
:private-bases:
:parts: 1

.. autosummary::
rateslib.instruments.FXCall
rateslib.instruments.FXPut
rateslib.instruments.FXRiskReversal

FXForwards Market
==================

As multi-currency derivatives, *FX Options* rely on the existence of an
:class:`~rateslib.fx.FXForwards` object, which is usually determined
from non-volatility markets. See :ref:`FX forwards <fxf-doc>`. This will be used to forecast
forward FX rates relevant to the pricing of an arbitrary *FX Option*.

For the purpose of this user guide page, we create such a market below.

.. ipython:: python

# FXForwards for FXOptions
eureur = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752}, calendar="tgt", id="eureur"
)
usdusd = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271}, calendar="nyc", id="usdusd"
)
eurusd = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id="eurusd"
)
fxr = FXRates({"eurusd": 1.0615}, settlement=dt(2023, 3, 20))
fxf = FXForwards(
fx_curves={"eureur": eureur, "eurusd": eurusd, "usdusd": usdusd},
fx_rates=fxr
)
fxf._set_ad_order(1)
fxf.swap("eurusd", [dt(2023, 3, 20), dt(2023, 6, 20)]) # should be 60.1 points

Building and Pricing an Option
================================

Typing `EURUSD Curncy OVML` into Bloomberg will bring up the Bloomberg currency options pricer.
This can be replicated with *rateslib* native functionality.

.. container:: twocol

.. container:: leftside40

.. ipython:: python

fxc = FXCall(
pair="eurusd",
expiry=dt(2023, 6, 16),
notional=20e6,
strike=1.101,
payment_lag=dt(2023, 3, 20),
delivery_lag=2,
calendar="tgt",
modifier="mf",
premium_ccy="usd",
eval_date=NoInput(0),
premium=NoInput(0),
option_fixing=NoInput(0),
delta_type="forward",
curves=NoInput(0),
spec=NoInput(0),
)
fxc.rate(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd","usd")],
fx=fxf,
vol=0.089
)
fxc.delta_percent(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=0.089
)

.. container:: rightside60

.. image:: _static/fx_opt_bbg_eurusd.png
:alt: Bloomberg EURUSD option pricer
:width: 400

.. raw:: html

<div class="clear"></div>

The *Call* option priced above is partly unpriced becuase the premium is not
directly specified. This means that *rateslib* will always assert the premium
to be mid-market, based on the prevailing *Curves*, *FXForwards* and *vol* parameters
supplied.

Changing some of the pricing parameters provides different prices. *Rateslib* is
compared to Bloomberg's OVML.

.. list-table::
:widths: 20 10 10 10 10 10 10 10 10
:header-rows: 3

* - Premium currency:
- usd
- usd
- usd
- usd
- eur
- eur
- eur
- eur
* - Premium date:
- 20/3/23
- 20/3/23
- 20/6/23
- 20/6/23
- 20/3/23
- 20/3/23
- 20/6/23
- 20/6/23
* - Delta type:
- Spot
- Forward
- Spot
- Forward
- Spot (pa)
- Forward (pa)
- Spot (pa)
- Forward (pa)
* - Option rate (*rateslib*):
- 69.3783
- 69.3783
- 70.2258
- 70.2258
- 0.65359
- 0.65359
- 0.65785
- 0.65785
* - Option rate (BBG):
- 69.378
- 69.378
- 70.226
- 70.226
- 0.6536
- 0.6536
- 0.6578
- 0.6578
* - Delta % (*rateslib*):
- 0.25012
- 0.25175
- 0.25012
- 0.25175
- 0.24359
- 0.24518
- 0.24359
- 0.24518
* - Delta % (BBG):
- 0.25012
- 0.25175
- 0.25013
- 0.25176
- 0.24359
- 0.24518
- 0.24355
- 0.24518

Restrictions
-------------

*Rateslib* currently allows the `currency` of the `premium` to **only be either** the domestic
(LHS) or the foreign (RHS) currency of the FX pair of the option (which is also the default
if none is specified).

If the currency is specified as foreign, then the pricing metric will
be stated in **pips** and the percent delta calculations are unadjusted.

If the currency is stated as domestic, then the pricing metric is stated as
**percentage of notional** and the percent delta calculations are **premium adjusted**.

Strikes given in Delta terms
=============================

Commonly interbank *Instruments* are quoted in terms of delta values and the strikes are not
explicitly stated. Suppose building a *FXCall* with a specified 25% delta.

.. ipython:: python

fxc = FXCall(
pair="eurusd",
expiry=dt(2023, 6, 16),
notional=20e6,
strike="25d",
payment_lag=2,
delivery_lag=2,
calendar="tgt",
premium_ccy="usd",
delta_type="spot",
)
fxc.rate(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd","usd")],
fx=fxf,
vol=0.089
)
fxc.delta_percent(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=0.089
)

When the pricing functions are called the **strike is implied** and automatically set on the
attached *FXCallPeriod*.

.. ipython:: python

fxc.periods[0].strike

If the pricing parameters change the *Option* strike will adapt accordingly to maintain the
25% spot delta calculation.

.. ipython:: python

fxc.rate(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd","usd")],
fx=fxf,
vol=0.10
)
fxc.delta_percent(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=0.10
)
fxc.periods[0].strike


Risk Reversals
================

:class:`~rateslib.instruments.FXRiskReversal` are included as a direct product
because they are frequently traded products and *Instruments* often used
in calibrating a volatility surface.

*RiskReversals* need to be specified by two different ``strike`` values; a
lower and a higher strike. These can be entered in delta terms. Pricing also allows
two different ``vol`` inputs in the absense of a volatility surface.

.. ipython:: python

fxrr = FXRiskReversal(
pair="eurusd",
expiry=dt(2023, 6, 16),
notional=20e6,
strike=("-25d", "25d"),
payment_lag=2,
delivery_lag=2,
calendar="tgt",
premium_ccy="usd",
delta_type="spot",
)
fxrr.rate(
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=[0.1015, 0.089]
)
fxrr.plot_payoff(
range=[1.025, 1.11],
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=[0.1015, 0.089]
)

.. plot::

from rateslib.curves import Curve
from rateslib.instruments import FXRiskReversal
from rateslib import dt
from rateslib.fx import FXForwards, FXRates

eureur = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.9851909811629752}, calendar="tgt", id="eureur"
)
usdusd = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.976009366603271}, calendar="nyc", id="usdusd"
)
eurusd = Curve(
{dt(2023, 3, 16): 1.0, dt(2023, 9, 16): 0.987092591908283}, id="eurusd"
)
fxr = FXRates({"eurusd": 1.0615}, settlement=dt(2023, 3, 20))
fxf = FXForwards(
fx_curves={"eureur": eureur, "eurusd": eurusd, "usdusd": usdusd},
fx_rates=fxr
)
fxrr = FXRiskReversal(
pair="eurusd",
expiry=dt(2023, 6, 16),
notional=20e6,
strike=("-25d", "25d"),
payment_lag=2,
delivery_lag=2,
calendar="tgt",
premium_ccy="usd",
delta_type="spot",
)
fxrr.plot_payoff(
range=[1.025, 1.11],
curves=[None, fxf.curve("eur", "usd"), None, fxf.curve("usd", "usd")],
fx=fxf,
vol=[0.1015, 0.089],
)
Loading
Loading