3131
3232class 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