Skip to content

Commit d92527a

Browse files
authored
DEV: FX options docs (#214) (#1132)
1 parent 429da75 commit d92527a

File tree

20 files changed

+376
-389
lines changed

20 files changed

+376
-389
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ version = "2.5.dev0"
1919
description = "A fixed income library for trading interest rates"
2020
readme = "README.md"
2121
authors = [{ name = "J H M Darbyshire"}]
22-
license = { file = "LICENSE" }
22+
license-files = ["LICEN[CS]E"]
2323
keywords = ["interest rate", "derivatives", "swaps", "bonds", "fixed income"]
2424
dependencies = [
2525
"numpy>=1.21.5,<3.0",

python/rateslib/curves/curves.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,11 @@ def index_value(
646646
2.375% and the RFR index for that date was 100.73350964. Below we calculate
647647
the value that was published for the RFR index on 7th Sep 2021 by the Riksbank.
648648
649+
.. ipython:: python
650+
:suppress:
651+
652+
from rateslib import Curve, dt
653+
649654
.. ipython:: python
650655
651656
index_curve = Curve(

python/rateslib/instruments/bonds/float_rate_note.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class FloatRateNote(_BaseBondInstrument):
182182
of the period rate when combining a ``float_spread``. Used **only** with RFR type
183183
``fixing_method``.
184184
rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`
185-
See XXX (working with fixings).
185+
See :ref:`Fixings <fixings-doc>`.
186186
The value of the rate fixing. If a scalar, is used directly. If a string identifier, links
187187
to the central ``fixings`` object and data loader.
188188

python/rateslib/instruments/fra.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class FRA(_BaseInstrument):
175175
:class:`~rateslib.data.fixings.FloatRateIndex`. If not given inherits attributes given
176176
such as the ``calendar``, ``convention``, ``method_param`` etc.
177177
leg2_rate_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`
178-
See XXX (working with fixings).
178+
See :ref:`Fixings <fixings-doc>`.
179179
The value of the rate fixing. If a scalar, is used directly. If a string identifier, links
180180
to the central ``fixings`` object and data loader.
181181

python/rateslib/instruments/fx_options/brokerfly.py

Lines changed: 71 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -31,33 +31,33 @@
3131

3232
class FXBrokerFly(_BaseFXOptionStrat):
3333
"""
34-
An *FX Strangle* :class:`~rateslib.instruments._FXOptionStrat`.
34+
An *FX BrokerFly* :class:`~rateslib.instruments._BaseFXOptionStrat`.
3535
36-
A *Straddle* is composed of a lower strike :class:`~rateslib.instruments.FXPut`
37-
and a higher strike :class:`~rateslib.instruments.FXCall`.
36+
A *BrokerFly* is composed of a :class:`~rateslib.instruments.FXStrangle`
37+
and a :class:`~rateslib.instruments.FXStraddle`.
3838
3939
.. rubric:: Examples
4040
4141
.. ipython:: python
4242
:suppress:
4343
44-
from rateslib.instruments import FXRiskReversal
45-
from datetime import datetime as dt
44+
from rateslib import FXBrokerFly, Curve, FXForwards, FXDeltaVolSmile, FXRates, dt
4645
4746
.. ipython:: python
4847
49-
fxc = FXRiskReversal(
48+
fxbf = FXBrokerFly(
5049
expiry="3m",
51-
strike="atm_delta",
50+
strike=[["-10d", "10d"], "atm_delta"],
5251
eval_date=dt(2020, 1, 1),
5352
spec="eurusd_call",
53+
notional=[1000000.0, None], # <- straddle notional is derived from vega neutral
5454
)
55-
fxc.cashflows()
55+
fxbf.cashflows()
5656
5757
.. rubric:: Pricing
5858
5959
The pricing mirrors that for an :class:`~rateslib.instruments.FXCall`.
60-
Allowable inputs are:
60+
All options use the same ``curves`. Allowable inputs are:
6161
6262
.. code-block:: python
6363
@@ -74,14 +74,53 @@ class FXBrokerFly(_BaseFXOptionStrat):
7474
.. code-block:: python
7575
7676
vol = 12.0 | vol_obj # a single item universally applied
77-
vol = [12.0, 12.0] # values for the Put and Call respectively
77+
vol = [[13.1, 13.4], 12.0] # values for Strangle and Straddle respectively
7878
79-
The pricing ``metric`` will return the following calculations:
79+
*BrokerFlys* inherit the peculiarities of an :class:`~rateslib.instruments.FXStrangle`.
80+
If the notional is not set on the *FXStraddle* then a calculation will be performed to derive a
81+
notional that yields a vega neutral strategy.
82+
The following pricing ``metric`` are available, with examples:
83+
84+
.. ipython:: python
85+
86+
eur = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.98})
87+
usd = Curve({dt(2020, 1, 1): 1.0, dt(2021, 1, 1): 0.96})
88+
fxf = FXForwards(
89+
fx_rates=FXRates({"eurusd": 1.10}, settlement=dt(2020, 1, 3)),
90+
fx_curves={"eureur": eur, "eurusd": eur, "usdusd": usd},
91+
)
92+
fxvs = FXDeltaVolSmile(
93+
nodes={0.25: 11.0, 0.5: 9.8, 0.75: 10.7},
94+
expiry=dt(2020, 4, 1),
95+
eval_date=dt(2020, 1, 1),
96+
delta_type="forward",
97+
)
98+
99+
- **'single_vol'**: this is the *'single_vol'* price of the *FXStrangle* minus the *'single_vol'*
100+
price of the *FXStraddle*. **'vol'** is an alias for single vol and returns the same value.
101+
102+
.. ipython:: python
103+
104+
fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric="single_vol")
105+
fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric="vol")
106+
107+
- **'premium'**: the summed cash premium amount, of both options, applicable to the 'payment'
108+
date. If *FXStrangle* strikes are given as delta percentages then they are first determined
109+
using the *'single_vol'*.
110+
111+
.. ipython:: python
112+
113+
fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric="premium")
114+
115+
- **'pips_or_%'**: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if
116+
the premium currency is RHS this gives a number of pips of the FX rate. Summed over both
117+
options. For *FXStrangle* strikes set with delta percentages these are first determined using the
118+
'single_vol'.
119+
120+
.. ipython:: python
121+
122+
fxbf.rate(vol=fxvs, curves=[eur, usd], fx=fxf, metric="pips_or_%")
80123
81-
- *'vol'*: the implied volatility value of the option from a volatility object.
82-
- *'premium'*: the cash premium amount applicable to the 'payment' date.
83-
- *'pips_or_%'*: if the premium currency is LHS of ``pair`` this is a % of notional, whilst if
84-
the premium currency is RHS this gives a number of pips of the FX rate.
85124
86125
.. role:: red
87126
@@ -99,12 +138,14 @@ class FXBrokerFly(_BaseFXOptionStrat):
99138
The expiry of the option. If given in string tenor format, e.g. "1M" requires an
100139
``eval_date``. See **Notes**.
101140
strike: 2-tuple of float, Variable, str, :red:`required`
102-
The strikes of the put and the call in order.
141+
The strikes of the *FXStrangle* and the *FXStraddle* in order.
103142
pair: str, :red:`required`
104143
The currency pair for the FX rate which settles the option, in 3-digit codes, e.g. "eurusd".
105144
May be included as part of ``spec``.
106-
notional: float, :green:`optional (set by 'defaults')`
107-
The notional amount of each option expressed in units of LHS of ``pair``.
145+
notional: 2-tuple of float or None, :green:`optional (set by 'defaults')`
146+
The notional amount of each option strategy expressed in units of LHS of ``pair``.
147+
If the straddle notional is given as None then it will be determined from the strangle
148+
notional under a vega neutral approach.
108149
eval_date: datetime, :green:`optional`
109150
Only required if ``expiry`` is given as string tenor.
110151
Should be entered as today (also called horizon) and **not** spot. Spot is derived
@@ -135,10 +176,10 @@ class FXBrokerFly(_BaseFXOptionStrat):
135176
136177
The following define additional **rate** parameters.
137178
138-
premium: 2-tuple of float, :green:`optional`
139-
The amount paid for the put and call in order. If not given assumes unpriced
179+
premium: 2-tuple of 2-tuple float, :green:`optional`
180+
The amount paid for each option in each strategy in order. If not given assumes unpriced
140181
*Options* and sets this as mid-market premium during pricing.
141-
option_fixings: 2-tuple of float, Dual, Dual2, Variable, Series, str, :green:`optional`
182+
option_fixings: float, Dual, Dual2, Variable, Series, str, :green:`optional`
142183
The value of each option's :class:`~rateslib.data.fixings.FXFixing`. If a scalar, is used
143184
directly. If a string identifier, links to the central ``fixings`` object and data loader.
144185
@@ -171,7 +212,7 @@ class FXBrokerFly(_BaseFXOptionStrat):
171212
:class:`~rateslib.instruments._FXOptionStrat` where the number
172213
of options and their definitions and nominals have been specifically overloaded for
173214
convenience.
174-
"""
215+
""" # noqa: E501
175216

176217
_rate_scalar = 100.0
177218

@@ -281,7 +322,6 @@ def _maybe_set_vega_neutral_notional(
281322
curves: CurvesT_,
282323
solver: Solver_,
283324
fx: FXForwards_,
284-
base: str_,
285325
vol: tuple[FXVolStrat_, FXVolStrat_],
286326
metric: str_,
287327
) -> None:
@@ -302,7 +342,7 @@ def _maybe_set_vega_neutral_notional(
302342
curves,
303343
solver,
304344
fx,
305-
base,
345+
base=NoInput(0),
306346
vol=vol[0],
307347
metric="single_vol",
308348
record_greeks=True,
@@ -313,7 +353,6 @@ def _maybe_set_vega_neutral_notional(
313353
curves,
314354
solver,
315355
fx,
316-
base,
317356
vol=vol[1],
318357
)
319358
strangle_vega = self._greeks["strangle"]["market_vol"]["FXPut"]["vega"]
@@ -338,26 +377,6 @@ def rate(
338377
forward: datetime_ = NoInput(0),
339378
metric: str_ = NoInput(0),
340379
) -> DualTypes:
341-
"""
342-
Returns the rate of the *FXBrokerFly* according to a pricing metric.
343-
344-
For parameters see :meth:`_FXOptionStrat.rate <rateslib.instruments._FXOptionStrat.rate>`.
345-
346-
Notes
347-
------
348-
349-
.. warning::
350-
351-
The default ``metric`` for an *FXBrokerFly* is *'single_vol'*, which requires an
352-
iterative algorithm to solve.
353-
For defined strikes it is usually very accurate but for strikes defined by delta it
354-
will return a solution within 0.01 pips. This means it is both slower than other
355-
instruments and inexact.
356-
357-
The ``metric`` *'vol'* is not sensible to use with an *FXBrokerFly*, although it will
358-
return the arithmetic average volatility across both strategies, *'single_vol'* is the
359-
more standardised choice.
360-
"""
361380
# Get curves and vol
362381
vol_ = tuple(
363382
[
@@ -368,7 +387,7 @@ def rate(
368387
_curves = self._parse_curves(curves)
369388

370389
metric_ = _drb(self.kwargs.meta["metric"], metric).lower()
371-
self._maybe_set_vega_neutral_notional(_curves, solver, fx, base, vol_, metric_)
390+
self._maybe_set_vega_neutral_notional(_curves, solver, fx, vol_, metric_)
372391

373392
if metric_ == "pips_or_%":
374393
straddle_scalar = (
@@ -402,17 +421,16 @@ def analytic_greeks(
402421
curves: CurvesT_ = NoInput(0),
403422
solver: Solver_ = NoInput(0),
404423
fx: FXForwards_ = NoInput(0),
405-
base: str_ = NoInput(0),
406424
vol: FXVolStrat_ = NoInput(0),
407425
) -> dict[str, Any]:
408426
# implicitly call set_pricing_mid for unpriced parameters
409-
self.rate(curves=curves, solver=solver, fx=fx, base=base, vol=vol, metric="pips_or_%")
427+
self.rate(curves=curves, solver=solver, fx=fx, base=NoInput(0), vol=vol, metric="pips_or_%")
410428

411429
vol_ = self._parse_vol(vol)
412430

413431
# TODO: this meth can be optimised because it calculates greeks at multiple times in frames
414-
g_grks = self.instruments[0].analytic_greeks(curves, solver, fx, base, vol_[0])
415-
d_grks = self.instruments[1].analytic_greeks(curves, solver, fx, base, vol_[1])
432+
g_grks = self.instruments[0].analytic_greeks(curves, solver, fx, vol_[0])
433+
d_grks = self.instruments[1].analytic_greeks(curves, solver, fx, vol_[1])
416434
sclr = abs(
417435
self.instruments[1].instruments[0]._option.settlement_params.notional
418436
/ self.instruments[0].instruments[0]._option.settlement_params.notional,
@@ -443,14 +461,12 @@ def analytic_greeks(
443461

444462
def _plot_payoff(
445463
self,
446-
window: list[float] | NoInput = NoInput(0), # noqa: A002
464+
window: tuple[float, float] | NoInput = NoInput(0), # noqa: A002
447465
curves: CurvesT_ = NoInput(0),
448466
solver: Solver_ = NoInput(0),
449467
fx: FXForwards_ = NoInput(0),
450-
base: str_ = NoInput(0),
451-
local: bool = False,
452468
vol: FXVolStrat_ = NoInput(0),
453469
) -> tuple[Any, Any]:
454470
vol_ = self._parse_vol(vol)
455-
self._maybe_set_vega_neutral_notional(curves, solver, fx, base, vol_, metric="pips_or_%")
456-
return super()._plot_payoff(window, curves, solver, fx, base, local, vol_)
471+
self._maybe_set_vega_neutral_notional(curves, solver, fx, vol_, metric="pips_or_%")
472+
return super()._plot_payoff(window, curves, solver, fx, vol_)

0 commit comments

Comments
 (0)