99from rateslib .instruments .protocols .kwargs import _convert_to_schedule_kwargs , _KWArgs
1010from 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 ,
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
0 commit comments