Skip to content

Commit 3106f5b

Browse files
attack68mikelync
andauthored
ENH: add ND to IRS directly (#198) (#1117)
Co-authored-by: Mike Lync <[email protected]> Co-authored-by: JHM Darbyshire (M1) <[email protected]>
1 parent 94f70cc commit 3106f5b

File tree

5 files changed

+390
-7
lines changed

5 files changed

+390
-7
lines changed

python/rateslib/instruments/irs.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from rateslib.instruments.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs
1010
from rateslib.instruments.protocols.pricing import (
1111
_Curves,
12+
_get_fx_forwards_maybe_from_solver,
1213
_maybe_get_curve_maybe_from_solver,
1314
_maybe_get_curve_or_dict_maybe_from_solver,
1415
_Vol,
@@ -22,10 +23,10 @@
2223
DataFrame,
2324
DualTypes,
2425
DualTypes_,
25-
FixingsRates_,
2626
FloatRateSeries,
2727
Frequency,
2828
FXForwards_,
29+
LegFixings,
2930
RollDay,
3031
Solver_,
3132
VolT_,
@@ -51,6 +52,7 @@ class IRS(_BaseInstrument):
5152
5253
from rateslib.instruments import IRS
5354
from datetime import datetime as dt
55+
from rateslib import fixings
5456
5557
.. ipython:: python
5658
@@ -200,6 +202,22 @@ class IRS(_BaseInstrument):
200202
The value of the rate fixing. If a scalar, is used directly. If a string identifier, links
201203
to the central ``fixings`` object and data loader.
202204
205+
.. note::
206+
207+
The following define **non-deliverability** parameters. If the swap is
208+
directly deliverable do not use these parameters. Review the **notes** section
209+
non-deliverability.
210+
211+
pair: str, :green:`optional`
212+
The currency pair for :class:`~rateslib.data.fixings.FXFixing` that determines *Period*
213+
settlement. The *reference currency* is implied from ``pair``. Must include ``currency``.
214+
fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`
215+
The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* according
216+
to non-deliverability.
217+
leg2_fx_fixings: float, Dual, Dual2, Variable, Series, str, 2-tuple or list, :green:`optional`
218+
The value of the :class:`~rateslib.data.fixings.FXFixing` for each *Period* on *Leg2*
219+
according to non-deliverability.
220+
203221
.. note::
204222
205223
The following are **meta parameters**.
@@ -211,6 +229,49 @@ class IRS(_BaseInstrument):
211229
A collective group of parameters. See
212230
:ref:`default argument specifications <defaults-arg-input>`.
213231
232+
Notes
233+
-----
234+
235+
**Non-Deliverable IRS (NDIRS)**
236+
237+
An *NDIRS* can be constructed by using the ``pair`` argument. The ``currency`` defines the
238+
*settlement currency*, whilst the *reference currency* is derived from ``pair`` and the
239+
``notional`` is expressed *reference currency* units.
240+
241+
The ``fx_fixings`` argument is typically used to provide an FX fixing series from which to
242+
extract non-deliverable :class:`~rateslib.data.fixings.FXFixing` data. The ``leg2_fx_fixings``
243+
inherits from the former and is likely to always be omitted, unless the fixings are provided
244+
as a list (against best practice) and the schedules do not align.
245+
246+
For **pricing**, whilst a traditional *IRS* can be priced with just one *Curve*, e.g. "sofr"
247+
for a conventional USD IRS, an ND-IRS will always require 2 different curves: a *leg2 rate
248+
curve* for forecasting rates in the non-deliverable reference currency, and a *disc curve* for
249+
discounting cashflows in the settlement currency.
250+
251+
The following is an example of a THB ND-IRS settled in USD with notional of 10mm THB.
252+
253+
.. ipython:: python
254+
255+
fixings.add("USDTHB", Series(index=[dt(2000, 7, 3), dt(2001, 1, 3)], data=[35.25, 37.0]))
256+
irs = IRS(
257+
effective=dt(2000, 1, 1),
258+
termination="2y",
259+
frequency="S",
260+
currency="usd", # <- USD set as the settlement currency
261+
pair="usdthb", # <- THB inferred as the reference currency
262+
fx_fixings="USDTHB",
263+
fixed_rate=2.0,
264+
# all other arguments set as normal IRS
265+
)
266+
irs.cashflows()
267+
268+
.. ipython:: python
269+
:suppress:
270+
271+
fixings.pop("USDTHB")
272+
273+
Further information is available in the documentation for a :class:`~rateslib.legs.FixedLeg`.
274+
214275
""" # noqa: E501
215276

216277
_rate_scalar = 1.0
@@ -290,11 +351,15 @@ def __init__(
290351
amortization: float_ = NoInput(0),
291352
leg2_notional: float_ = NoInput(-1),
292353
leg2_amortization: float_ = NoInput(-1),
354+
# non-deliverability
355+
pair: str_ = NoInput(0),
356+
fx_fixings: LegFixings = NoInput(0),
357+
leg2_fx_fixings: LegFixings = NoInput(1),
293358
# rate parameters
294359
fixed_rate: DualTypes_ = NoInput(0),
295360
leg2_float_spread: DualTypes_ = NoInput(0),
296361
leg2_spread_compound_method: str_ = NoInput(0),
297-
leg2_rate_fixings: FixingsRates_ = NoInput(0),
362+
leg2_rate_fixings: LegFixings = NoInput(0),
298363
leg2_fixing_method: str_ = NoInput(0),
299364
leg2_method_param: int_ = NoInput(0),
300365
leg2_fixing_frequency: Frequency | str_ = NoInput(0),
@@ -339,6 +404,10 @@ def __init__(
339404
leg2_notional=leg2_notional,
340405
amortization=amortization,
341406
leg2_amortization=leg2_amortization,
407+
# non-deliverability
408+
pair=pair,
409+
fx_fixings=fx_fixings,
410+
leg2_fx_fixings=leg2_fx_fixings,
342411
# rate
343412
fixed_rate=fixed_rate,
344413
leg2_float_spread=leg2_float_spread,
@@ -353,11 +422,14 @@ def __init__(
353422
)
354423
instrument_args = dict( # these are hard coded arguments specific to this instrument
355424
leg2_currency=NoInput(1),
425+
leg2_pair=NoInput(1),
356426
initial_exchange=False,
357427
final_exchange=False,
358428
leg2_initial_exchange=False,
359429
leg2_final_exchange=False,
360430
vol=_Vol(),
431+
mtm=not isinstance(pair, NoInput),
432+
leg2_mtm=not isinstance(pair, NoInput),
361433
)
362434

363435
default_args = dict(
@@ -389,6 +461,7 @@ def rate(
389461
metric: str_ = NoInput(0),
390462
) -> DualTypes:
391463
_curves = self._parse_curves(curves)
464+
fx_ = _get_fx_forwards_maybe_from_solver(solver, fx)
392465

393466
leg2_npv: DualTypes = self.leg2.local_npv(
394467
rate_curve=_maybe_get_curve_or_dict_maybe_from_solver(
@@ -398,6 +471,7 @@ def rate(
398471
self.kwargs.meta["curves"], _curves, "leg2_disc_curve", solver
399472
),
400473
index_curve=NoInput(0),
474+
fx=fx_,
401475
settlement=settlement,
402476
forward=forward,
403477
)
@@ -408,6 +482,7 @@ def rate(
408482
disc_curve=_maybe_get_curve_maybe_from_solver(
409483
self.kwargs.meta["curves"], _curves, "disc_curve", solver
410484
),
485+
fx=fx_,
411486
index_curve=NoInput(0),
412487
settlement=settlement,
413488
forward=forward,
@@ -427,6 +502,7 @@ def spread(
427502
forward: datetime_ = NoInput(0),
428503
) -> DualTypes:
429504
_curves = self._parse_curves(curves)
505+
fx_ = _get_fx_forwards_maybe_from_solver(solver, fx)
430506
leg2_rate_curve = _maybe_get_curve_or_dict_maybe_from_solver(
431507
self.kwargs.meta["curves"], _curves, "leg2_rate_curve", solver
432508
)
@@ -437,12 +513,14 @@ def spread(
437513
rate_curve=NoInput(0),
438514
disc_curve=disc_curve,
439515
index_curve=NoInput(0),
516+
fx=fx_,
440517
settlement=settlement,
441518
forward=forward,
442519
)
443520
return self.leg2.spread(
444521
target_npv=-leg1_npv,
445522
rate_curve=leg2_rate_curve,
523+
fx=fx_,
446524
disc_curve=disc_curve,
447525
index_curve=NoInput(0),
448526
settlement=settlement,
@@ -464,6 +542,7 @@ def npv(
464542
self._set_pricing_mid(
465543
curves=curves,
466544
solver=solver,
545+
fx=fx,
467546
settlement=settlement,
468547
forward=forward,
469548
)
@@ -482,6 +561,7 @@ def _set_pricing_mid(
482561
self,
483562
curves: CurvesT_ = NoInput(0),
484563
solver: Solver_ = NoInput(0),
564+
fx: FXForwards_ = NoInput(0),
485565
settlement: datetime_ = NoInput(0),
486566
forward: datetime_ = NoInput(0),
487567
) -> None:
@@ -493,6 +573,7 @@ def _set_pricing_mid(
493573
solver=solver,
494574
settlement=settlement,
495575
forward=forward,
576+
fx=fx,
496577
)
497578
self.leg1.fixed_rate = _dual_float(mid_market_rate)
498579

python/rateslib/instruments/zcis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class ZCIS(_BaseInstrument):
6666
leg2_index_method="daily",
6767
)
6868
zcis.cashflows()
69-
69+
7070
.. ipython:: python
7171
:suppress:
7272
@@ -182,7 +182,7 @@ class ZCIS(_BaseInstrument):
182182
fixed_rate : float or None
183183
The fixed rate applied to the :class:`~rateslib.legs.ZeroFixedLeg`. If `None`
184184
will be set to mid-market when curves are provided.
185-
185+
186186
.. note::
187187
188188
The following parameters define **indexation**.

python/rateslib/periods/fx_volatility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,7 @@ def __init__(
10381038
notional: DualTypes_ = NoInput(0),
10391039
delta_type: FXDeltaMethod | str_ = NoInput(0),
10401040
metric: FXOptionMetric | str_ = NoInput(0),
1041-
option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0), # type: ignore[type-var]
1041+
option_fixings: DualTypes | Series[DualTypes] | str_ = NoInput(0), # type: ignore[type-var]
10421042
# currency args:
10431043
ex_dividend: datetime_ = NoInput(0),
10441044
) -> None:

python/rateslib/periods/parameters/fx_volatility.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class _FXOptionParams:
2222
"""
2323
Parameters for *FX Option Period* cashflows.
2424
"""
25+
2526
_expiry: datetime
2627
_delivery: datetime
2728
_pair: str

0 commit comments

Comments
 (0)