diff --git a/pyproject.toml b/pyproject.toml index 5f7bd8c3..e579aa37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -152,15 +152,15 @@ exclude = [ "/instruments/components/bonds", "/instruments/components/fx_options", # "/instruments/components/protocols", - "/instruments/components/cds.py", + # "/instruments/components/cds.py", "/instruments/components/fly.py", # "/instruments/components/fra.py", - "/instruments/components/fx_forward.py", - "/instruments/components/fx_swap.py", + # "/instruments/components/fx_forward.py", + # "/instruments/components/fx_swap.py", "/instruments/components/fx_vol_value.py", # "/instruments/components/iirs.py", # "/instruments/components/irs.py", - "/instruments/components/ndf.py", + # "/instruments/components/ndf.py", "/instruments/components/portfolio.py", # "/instruments/components/sbs.py", "/instruments/components/spread.py", diff --git a/python/rateslib/curves/__init__.py b/python/rateslib/curves/__init__.py index b2b25ec6..4d81fcfd 100644 --- a/python/rateslib/curves/__init__.py +++ b/python/rateslib/curves/__init__.py @@ -25,12 +25,18 @@ ) __all__ = ( - "CompositeCurve", "Curve", "LineCurve", + "CompositeCurve", "MultiCsaCurve", "ProxyCurve", "CreditImpliedCurve", + "RolledCurve", + "ShiftedCurve", + "TranslatedCurve", + "_BaseCurve", + "_WithOperations", + "_WithMutability", "average_rate", "index_left", "index_value", @@ -40,10 +46,4 @@ "_CurveInterpolator", "_CurveNodes", "_ProxyCurveInterpolator", - "RolledCurve", - "ShiftedCurve", - "TranslatedCurve", - "_WithOperations", - "_BaseCurve", - "_WithMutability", ) diff --git a/python/rateslib/enums/__init__.py b/python/rateslib/enums/__init__.py index 6204730a..9fb0a190 100644 --- a/python/rateslib/enums/__init__.py +++ b/python/rateslib/enums/__init__.py @@ -8,13 +8,13 @@ ) __all__ = [ - "Ok", - "Err", - "Result", - "NoInput", "FloatFixingMethod", - "IndexMethod", "SpreadCompoundMethod", + "IndexMethod", "FXDeltaMethod", "FXOptionMetric", + "NoInput", + "Result", + "Ok", + "Err", ] diff --git a/python/rateslib/instruments/components/cds.py b/python/rateslib/instruments/components/cds.py index 19be5034..ff678e58 100644 --- a/python/rateslib/instruments/components/cds.py +++ b/python/rateslib/instruments/components/cds.py @@ -9,7 +9,7 @@ from rateslib.instruments.components.protocols.kwargs import _convert_to_schedule_kwargs, _KWArgs from rateslib.instruments.components.protocols.pricing import ( _Curves, - _maybe_get_curve_or_dict_maybe_from_solver, + _maybe_get_curve_maybe_from_solver, ) from rateslib.legs.components import CreditPremiumLeg, CreditProtectionLeg from rateslib.scheduling import Frequency @@ -17,11 +17,10 @@ if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover CalInput, - Curves_, + CurvesT_, DataFrame, DualTypes, DualTypes_, - Frequency, FXForwards_, FXVolOption_, RollDay, @@ -37,6 +36,167 @@ class CDS(_BaseInstrument): + """ + A *credit default swap (CDS)* composing a :class:`~rateslib.legs.components.CreditPremiumLeg` + and a :class:`~rateslib.legs.components.CreditProtectionLeg`. + + .. rubric:: Examples + + .. ipython:: python + :suppress: + + from rateslib.instruments.components import CDS + from datetime import datetime as dt + + .. ipython:: python + + irs = CDS( + effective=dt(2001, 12, 20), + termination="2y", + spec="us_ig_cds", + ) + irs.cashflows() + + .. rubric:: Pricing + + A *CDS* requires a hazard *rate curve* and a *disc curve* on both legs + (which should be the same). The following input formats are + allowed: + + .. code-block:: python + + curves = [rate_curve, disc_curve] # two curves are applied in the given order + curves = [rate_curve, disc_curve, rate_curve, disc_curve] # four curves applied to each leg + curves = {"rate_curve": rate_curve, "disc_curve": disc_curve} + curves = { # dict form is explicit + "rate_curve": rate_curve, + "disc_curve": disc_curve + "leg2_rate_curve": rate_curve, + "leg2_disc_curve": rate_curve, + } + + .. role:: red + + .. role:: green + + Parameters + ---------- + . + + .. note:: + + The following define generalised **scheduling** parameters. + + effective : datetime, :red:`required` + The unadjusted effective date. If given as adjusted, unadjusted alternatives may be + inferred. + termination : datetime, str, :red:`required` + The unadjusted termination date. If given as adjusted, unadjusted alternatives may be + inferred. If given as string tenor will be calculated from ``effective``. + frequency : Frequency, str, :red:`required` + The frequency of the schedule. + If given as string will derive a :class:`~rateslib.scheduling.Frequency` aligning with: + monthly ("M"), quarterly ("Q"), semi-annually ("S"), annually("A") or zero-coupon ("Z"), or + a set number of calendar or business days ("_D", "_B"), weeks ("_W"), months ("_M") or + years ("_Y"). + Where required, the :class:`~rateslib.scheduling.RollDay` is derived as per ``roll`` + and business day calendar as per ``calendar``. + stub : StubInference, str in {"ShortFront", "LongFront", "ShortBack", "LongBack"}, :green:`optional` + The stub type used if stub inference is required. If given as string will derive a + :class:`~rateslib.scheduling.StubInference`. + front_stub : datetime, :green:`optional` + The unadjusted date for the start stub period. If given as adjusted, unadjusted + alternatives may be inferred. + back_stub : datetime, :green:`optional` + The unadjusted date for the back stub period. If given as adjusted, unadjusted + alternatives may be inferred. + See notes for combining ``stub``, ``front_stub`` and ``back_stub`` + and any automatic stub inference. + roll : RollDay, int in [1, 31], str in {"eom", "imm", "som"}, :green:`optional` + The roll day of the schedule. If not given or not available in ``frequency`` will be + inferred for monthly frequency variants. + eom : bool, :green:`optional` + Use an end of month preference rather than regular rolls for ``roll`` inference. Set by + default. Not required if ``roll`` is defined. + modifier : Adjuster, str in {"NONE", "F", "MF", "P", "MP"}, :green:`optional` + The :class:`~rateslib.scheduling.Adjuster` used for adjusting unadjusted schedule dates + into adjusted dates. If given as string must define simple date rolling rules. + calendar : calendar, str, :green:`optional` + The business day calendar object to use. If string will call + :meth:`~rateslib.scheduling.get_calendar`. + payment_lag: Adjuster, int, :green:`optional` + The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into + a payment date. If given as integer will define the number of business days to + lag payments by. + payment_lag_exchange: Adjuster, int, :green:`optional` + The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into + additional payment date. If given as integer will define the number of business days to + lag payments by. + ex_div: Adjuster, int, :green:`optional` + The :class:`~rateslib.scheduling.Adjuster` to use to map adjusted schedule dates into + additional dates, which may be used, for example by fixings schedules. If given as integer + will define the number of business days to lag dates by. + convention: str, :green:`optional (set by 'defaults')` + The day count convention applied to calculations of period accrual dates. + See :meth:`~rateslib.scheduling.dcf`. + leg2_effective : datetime, :green:`optional (inherited from leg1)` + leg2_termination : datetime, str, :green:`optional (inherited from leg1)` + leg2_frequency : Frequency, str, :green:`optional (inherited from leg1)` + leg2_stub : StubInference, str, :green:`optional (inherited from leg1)` + leg2_front_stub : datetime, :green:`optional (inherited from leg1)` + leg2_back_stub : datetime, :green:`optional (inherited from leg1)` + leg2_roll : RollDay, int, str, :green:`optional (inherited from leg1)` + leg2_eom : bool, :green:`optional (inherited from leg1)` + leg2_modifier : Adjuster, str, :green:`optional (inherited from leg1)` + leg2_calendar : calendar, str, :green:`optional (inherited from leg1)` + leg2_payment_lag: Adjuster, int, :green:`optional (inherited from leg1)` + leg2_payment_lag_exchange: Adjuster, int, :green:`optional (inherited from leg1)` + leg2_ex_div: Adjuster, int, :green:`optional (inherited from leg1)` + leg2_convention: str, :green:`optional (inherited from leg1)` + + .. note:: + + The following define generalised **settlement** parameters. + + currency : str, :green:`optional (set by 'defaults')` + The local settlement currency of the *Instrument* (3-digit code). + notional : float, Dual, Dual2, Variable, :green:`optional (set by 'defaults')` + The initial leg notional, defined in units of *reference currency*. + amortization: float, Dual, Dual2, Variable, str, Amortization, :green:`optional (set as zero)` + Set a non-constant notional per *Period*. If a scalar value, adjusts the ``notional`` of + each successive period by that same value. Should have + sign equal to that of notional if the notional is to reduce towards zero. + leg2_notional : float, Dual, Dual2, Variable, :green:`optional (negatively inherited from leg1)` + leg2_amortization : float, Dual, Dual2, Variable, str, Amortization, :green:`optional (negatively inherited from leg1)` + + .. note:: + + The following are **rate parameters**. + + fixed_rate : float or None + The fixed rate applied to the :class:`~rateslib.legs.FixedLeg`. If `None` + will be set to mid-market when curves are provided. + + .. note:: + + The following parameters define **credit specific** elements. + + premium_accrued: bool, :green:`optional (set by 'defaults')` + Whether an accrued premium is paid on the event of mid-period credit default. + + .. note:: + + The following are **meta parameters**. + + curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional` + Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See + **Pricing**. + spec: str, :green:`optional` + A collective group of parameters. See + :ref:`default argument specifications `. + + """ # noqa: E501 + _rate_scalar = 1.0 @property @@ -69,8 +229,6 @@ def __init__( termination: datetime | str_ = NoInput(0), frequency: Frequency | str_ = NoInput(0), *, - premium_accrued: bool_ = NoInput(0), - fixed_rate: DualTypes_ = NoInput(0), stub: str_ = NoInput(0), front_stub: datetime_ = NoInput(0), back_stub: datetime_ = NoInput(0), @@ -81,9 +239,6 @@ def __init__( payment_lag: int_ = NoInput(0), payment_lag_exchange: int_ = NoInput(0), ex_div: int_ = NoInput(0), - notional: float_ = NoInput(0), - currency: str_ = NoInput(0), - amortization: float_ = NoInput(0), convention: str_ = NoInput(0), leg2_effective: datetime_ = NoInput(1), leg2_termination: datetime | str_ = NoInput(1), @@ -97,19 +252,25 @@ def __init__( leg2_calendar: CalInput = NoInput(1), leg2_payment_lag: int_ = NoInput(1), leg2_payment_lag_exchange: int_ = NoInput(1), - leg2_notional: float_ = NoInput(-1), - leg2_amortization: float_ = NoInput(-1), leg2_convention: str_ = NoInput(1), leg2_ex_div: int_ = NoInput(1), - curves: Curves_ = NoInput(0), + # settlement + notional: float_ = NoInput(0), + currency: str_ = NoInput(0), + amortization: float_ = NoInput(0), + leg2_notional: float_ = NoInput(-1), + leg2_amortization: float_ = NoInput(-1), + # rate and credit params + premium_accrued: bool_ = NoInput(0), + fixed_rate: DualTypes_ = NoInput(0), + # meta params + curves: CurvesT_ = NoInput(0), spec: str_ = NoInput(0), ) -> None: user_args = dict( effective=effective, termination=termination, frequency=frequency, - premium_accrued=premium_accrued, - fixed_rate=fixed_rate, stub=stub, front_stub=front_stub, back_stub=back_stub, @@ -124,11 +285,6 @@ def __init__( currency=currency, amortization=amortization, convention=convention, - # leg2_float_spread=leg2_float_spread, - # leg2_spread_compound_method=leg2_spread_compound_method, - # leg2_rate_fixings=leg2_rate_fixings, - # leg2_fixing_method=leg2_fixing_method, - # leg2_method_param=leg2_method_param, leg2_effective=leg2_effective, leg2_termination=leg2_termination, leg2_frequency=leg2_frequency, @@ -145,14 +301,14 @@ def __init__( leg2_notional=leg2_notional, leg2_amortization=leg2_amortization, leg2_convention=leg2_convention, + # rate and credit + premium_accrued=premium_accrued, + fixed_rate=fixed_rate, + # meta curves=self._parse_curves(curves), ) instrument_args = dict( # these are hard coded arguments specific to this instrument leg2_currency=NoInput(1), - # initial_exchange=False, - # final_exchange=False, - # leg2_initial_exchange=False, - # leg2_final_exchange=False, ) default_args = dict( @@ -176,7 +332,7 @@ def __init__( def rate( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -188,10 +344,10 @@ def rate( _curves = self._parse_curves(curves) leg2_npv: DualTypes = self.leg2.local_npv( - rate_curve=_maybe_get_curve_or_dict_maybe_from_solver( + rate_curve=_maybe_get_curve_maybe_from_solver( self.kwargs.meta["curves"], _curves, "leg2_rate_curve", solver ), - disc_curve=_maybe_get_curve_or_dict_maybe_from_solver( + disc_curve=_maybe_get_curve_maybe_from_solver( self.kwargs.meta["curves"], _curves, "leg2_disc_curve", solver ), index_curve=NoInput(0), @@ -201,10 +357,10 @@ def rate( return ( self.leg1.spread( target_npv=-leg2_npv, - rate_curve=_maybe_get_curve_or_dict_maybe_from_solver( + rate_curve=_maybe_get_curve_maybe_from_solver( self.kwargs.meta["curves"], _curves, "rate_curve", solver ), - disc_curve=_maybe_get_curve_or_dict_maybe_from_solver( + disc_curve=_maybe_get_curve_maybe_from_solver( self.kwargs.meta["curves"], _curves, "disc_curve", solver ), index_curve=NoInput(0), @@ -236,7 +392,7 @@ def accrued(self, settlement: datetime) -> DualTypes: def spread( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -260,7 +416,7 @@ def spread( def npv( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -288,7 +444,7 @@ def npv( def _set_pricing_mid( self, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), settlement: datetime_ = NoInput(0), forward: datetime_ = NoInput(0), @@ -304,7 +460,7 @@ def _set_pricing_mid( ) self.leg1.fixed_rate = _dual_float(mid_market_rate) - def _parse_curves(self, curves: Curves_) -> _Curves: + def _parse_curves(self, curves: CurvesT_) -> _Curves: """ A CDS has two curve requirements: a hazard_curve and a disc_curve used by both legs. @@ -333,6 +489,13 @@ def _parse_curves(self, curves: Curves_) -> _Curves: disc_curve=curves[1], leg2_disc_curve=curves[1], ) + elif len(curves) == 4: + return _Curves( + rate_curve=curves[0], + leg2_rate_curve=curves[2], + disc_curve=curves[1], + leg2_disc_curve=curves[3], + ) else: raise ValueError(f"{type(self).__name__} requires 2 `curves`. Got {len(curves)}.") @@ -342,7 +505,7 @@ def _parse_curves(self, curves: Curves_) -> _Curves: def cashflows( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -363,7 +526,7 @@ def cashflows( def local_analytic_rate_fixings( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), diff --git a/python/rateslib/instruments/components/fra.py b/python/rateslib/instruments/components/fra.py index 89140dc1..b38f7f99 100644 --- a/python/rateslib/instruments/components/fra.py +++ b/python/rateslib/instruments/components/fra.py @@ -175,7 +175,7 @@ class FRA(_BaseInstrument): The value of the rate fixing. If a scalar, is used directly. If a string identifier, links to the central ``fixings`` object and data loader. - """ + """ # noqa: E501 _rate_scalar = 1.0 diff --git a/python/rateslib/instruments/components/fx_forward.py b/python/rateslib/instruments/components/fx_forward.py index bdc58e3e..fc594eb3 100644 --- a/python/rateslib/instruments/components/fx_forward.py +++ b/python/rateslib/instruments/components/fx_forward.py @@ -10,20 +10,21 @@ from rateslib.instruments.components.protocols.pricing import ( _Curves, _get_fx_maybe_from_solver, - _maybe_get_curve_or_dict_maybe_from_solver, + _maybe_get_curve_maybe_from_solver, ) from rateslib.legs.components import CustomLeg from rateslib.periods.components import Cashflow +from rateslib.periods.components.utils import _validate_base_curve if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover - CurveOption_, - Curves_, + CurvesT_, DataFrame, DualTypes, DualTypes_, FXForwards_, FXVolOption_, + Sequence, Solver_, _BaseLeg, datetime, @@ -34,7 +35,7 @@ class FXForward(_BaseInstrument): """ - Create a simple *FX exchange* composing two + A dated *FX exchange* composing two :class:`~rateslib.legs.components.CustomLeg` of individual :class:`~rateslib.periods.components.Cashflow` of different currencies. @@ -58,6 +59,18 @@ class FXForward(_BaseInstrument): ) fxfwd.cashflows() + .. rubric:: Pricing + + An *FX Forward* requires a *disc curve* and a *leg2 disc curve* to discount the cashflows + of the respective currencies (typically with the same collateral definition). + The following input formats are allowed: + + .. code-block:: python + + curves = [disc_curve, leg2_disc_curve] # two curves are applied in the given order + curves = [None, disc_curve, None, leg2_disc_curve] # four curves applied to each leg + curves = {"disc_curve": disc_curve, "leg2_disc_curve": leg2_disc_curve} # dict form is explicit + .. role:: red .. role:: green @@ -79,7 +92,7 @@ class FXForward(_BaseInstrument): For *FXExchange* only discounting curves are required in each currency and not rate forecasting curves. The signature should be: `[None, eur_curve, None, usd_curve]` for a "eurusd" pair. - """ + """ # noqa: E501 _rate_scalar = 1.0 @@ -94,13 +107,13 @@ def leg2(self) -> CustomLeg: return self._leg2 @property - def legs(self) -> list[_BaseLeg]: + def legs(self) -> Sequence[_BaseLeg]: """A list of the *Legs* of the *Instrument*.""" return self._legs - def _parse_curves(self, curves: CurveOption_) -> _Curves: + def _parse_curves(self, curves: CurvesT_) -> _Curves: """ - An FXExchange requires 2 curves a separate discount curve for each currency + An FXExchange requires 2 curves; a disc_curve and leg2_disc_curve. When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve. """ @@ -109,43 +122,28 @@ def _parse_curves(self, curves: CurveOption_) -> _Curves: elif isinstance(curves, dict): return _Curves( disc_curve=curves.get("disc_curve", NoInput(0)), - index_curve=curves.get("index_curve", NoInput(0)), - leg2_rate_curve=_drb( - curves.get("rate_curve", NoInput(0)), - curves.get("leg2_rate_curve", NoInput(0)), - ), leg2_disc_curve=_drb( curves.get("disc_curve", NoInput(0)), curves.get("leg2_disc_curve", NoInput(0)), ), ) elif isinstance(curves, list | tuple): - if len(curves) == 3: + if len(curves) == 2: return _Curves( - disc_curve=curves[1], - index_curve=curves[0], - leg2_rate_curve=curves[2], - leg2_disc_curve=curves[1], - ) - elif len(curves) == 2: - return _Curves( - disc_curve=curves[1], - index_curve=curves[0], - leg2_rate_curve=curves[1], + disc_curve=curves[0], leg2_disc_curve=curves[1], ) elif len(curves) == 4: return _Curves( disc_curve=curves[1], - index_curve=curves[0], - leg2_rate_curve=curves[2], leg2_disc_curve=curves[3], ) else: raise ValueError( f"{type(self).__name__} requires 2 curve types. Got {len(curves)}." ) - + elif isinstance(curves, _Curves): + return curves else: # `curves` is just a single input which is copied across all curves raise ValueError(f"{type(self).__name__} requires 2 curve types. Got 1.") @@ -156,7 +154,7 @@ def __init__( fx_rate: DualTypes_ = NoInput(0), notional: DualTypes_ = NoInput(0), leg2_notional: DualTypes_ = NoInput(0), - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), ): pair_ = pair.lower() if isinstance(notional, NoInput) and isinstance(leg2_notional, NoInput): @@ -191,6 +189,7 @@ def __init__( # allocate arguments to correct legs for non-deliverability if isinstance(notional, NoInput): + # both notionals cannot be NoInput so leg2_notional is assumed given self.kwargs.leg1["notional"] = -1.0 * self.kwargs.leg2["notional"] self.kwargs.leg1["pair"] = pair_ self.kwargs.leg1["fx_fixings"] = fx_rate @@ -226,7 +225,7 @@ def __init__( def cashflows( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -247,7 +246,7 @@ def cashflows( def rate( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -255,7 +254,7 @@ def rate( settlement: datetime_ = NoInput(0), forward: datetime_ = NoInput(0), metric: str_ = NoInput(0), - ) -> DualTypes_: + ) -> DualTypes: _curves = self._parse_curves(curves) fx_ = _get_fx_maybe_from_solver(solver=solver, fx=fx) if isinstance(fx_, FXRates | FXForwards): @@ -268,14 +267,17 @@ def rate( else: imm_fx = fx_ + curve_domestic = _maybe_get_curve_maybe_from_solver( + self.kwargs.meta["curves"], _curves, "disc_curve", solver + ) + curve_foreign = _maybe_get_curve_maybe_from_solver( + self.kwargs.meta["curves"], _curves, "leg2_disc_curve", solver + ) + _: DualTypes = forward_fx( date=self.kwargs.leg1["settlement"], - curve_domestic=_maybe_get_curve_or_dict_maybe_from_solver( - self.kwargs.meta["curves"], _curves, "disc_curve", solver - ), - curve_foreign=_maybe_get_curve_or_dict_maybe_from_solver( - self.kwargs.meta["curves"], _curves, "leg2_disc_curve", solver - ), + curve_domestic=_validate_base_curve(curve_domestic), + curve_foreign=_validate_base_curve(curve_foreign), fx_rate=imm_fx, ) return _ diff --git a/python/rateslib/instruments/components/fx_swap.py b/python/rateslib/instruments/components/fx_swap.py index 834fbe5d..95868d9c 100644 --- a/python/rateslib/instruments/components/fx_swap.py +++ b/python/rateslib/instruments/components/fx_swap.py @@ -4,23 +4,24 @@ from typing import TYPE_CHECKING from rateslib import defaults -from rateslib.enums.generics import NoInput, _drb +from rateslib.enums.generics import NoInput from rateslib.instruments.components.protocols import _BaseInstrument from rateslib.instruments.components.protocols.kwargs import _KWArgs from rateslib.instruments.components.protocols.pricing import ( _Curves, - _get_fx_maybe_from_solver, - _maybe_get_curve_or_dict_maybe_from_solver, + _get_fx_forwards_maybe_from_solver, + _maybe_get_curve_maybe_from_solver, ) from rateslib.legs.components import CustomLeg from rateslib.periods.components import Cashflow +from rateslib.periods.components.utils import _validate_base_curve from rateslib.scheduling import Schedule if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover + Any, CalInput, - CurveOption_, - Curves_, + CurvesT_, DataFrame, DualTypes, DualTypes_, @@ -28,6 +29,7 @@ FXVolOption_, PeriodFixings, RollDay, + Sequence, Solver_, _BaseLeg, bool_, @@ -66,6 +68,18 @@ class FXSwap(_BaseInstrument): ) fxs.cashflows() + .. rubric:: Pricing + + An *FX Swap* requires a *disc curve* and a *leg2 disc curve* to discount the cashflows + of the respective currencies (typically with the same collateral definition). + The following input formats are allowed: + + .. code-block:: python + + curves = [disc_curve, leg2_disc_curve] # two curves are applied in the given order + curves = [None, disc_curve, None, leg2_disc_curve] # four curves applied to each leg + curves = {"disc_curve": disc_curve, "leg2_disc_curve": leg2_disc_curve} # dict form is explicit + .. role:: red .. role:: green @@ -147,25 +161,20 @@ def leg2(self) -> CustomLeg: return self._leg2 @property - def legs(self) -> list[_BaseLeg]: + def legs(self) -> Sequence[_BaseLeg]: """A list of the *Legs* of the *Instrument*.""" return self._legs - def _parse_curves(self, curves: CurveOption_) -> _Curves: + def _parse_curves(self, curves: CurvesT_) -> _Curves: """ - An NDF requires 1 curve to discount curve the settlement currency. - - When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve. + An FXSwap requires a disc curve and a leg2 disc curve """ if isinstance(curves, NoInput): return _Curves() elif isinstance(curves, dict): return _Curves( disc_curve=curves.get("disc_curve", NoInput(0)), - leg2_disc_curve=_drb( - curves.get("disc_curve", NoInput(0)), - curves.get("leg2_disc_curve", NoInput(0)), - ), + leg2_disc_curve=curves.get("leg2_disc_curve", NoInput(0)), ) elif isinstance(curves, list | tuple): if len(curves) == 2: @@ -180,14 +189,12 @@ def _parse_curves(self, curves: CurveOption_) -> _Curves: ) else: raise ValueError( - f"{type(self).__name__} requires 1 curve types. Got {len(curves)}." + f"{type(self).__name__} requires 2 curve types. Got {len(curves)}." ) - + elif isinstance(curves, _Curves): + return curves else: # `curves` is just a single input which is copied across all curves - return _Curves( - disc_curve=curves, - leg2_disc_curve=curves, - ) + raise ValueError(f"{type(self).__name__} requires 2 curve types. Got 1.") def __init__( self, @@ -209,7 +216,7 @@ def __init__( leg2_fx_fixings: PeriodFixings = NoInput(0), points: DualTypes_ = NoInput(0), # meta - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), spec: str_ = NoInput(0), ): pair_ = pair.lower() @@ -256,7 +263,7 @@ def __init__( pair=NoInput(0), leg2_pair=NoInput(0), ) - default_args = dict() + default_args: dict[str, Any] = dict() self._kwargs = _KWArgs( spec=spec, user_args={**user_args, **instrument_args}, @@ -395,7 +402,7 @@ def _validate_init_combinations( def cashflows( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -416,7 +423,7 @@ def cashflows( def rate( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -424,7 +431,7 @@ def rate( settlement: datetime_ = NoInput(0), forward: datetime_ = NoInput(0), metric: str_ = NoInput(0), - ) -> DualTypes_: + ) -> DualTypes: if isinstance(self.kwargs.leg1["pair"], NoInput): # then non-deliverability and fx_fixing are on leg2 return self._rate_on_leg( @@ -441,41 +448,48 @@ def _rate_on_leg( core_leg: str, nd_leg: str, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), - ) -> DualTypes_: + ) -> DualTypes: _curves = self._parse_curves(curves) - fx_ = _get_fx_maybe_from_solver(solver=solver, fx=fx) + fx_ = _get_fx_forwards_maybe_from_solver(solver=solver, fx=fx) core_curve = "" if core_leg == "leg1" else "leg2_" nd_curve = "" if nd_leg == "leg1" else "leg2_" - core_leg = getattr(self, core_leg) - nd_leg = getattr(self, nd_leg) + core_leg_: CustomLeg = getattr(self, core_leg) + nd_leg_: CustomLeg = getattr(self, nd_leg) # then non-deliverability and fx_fixing are on leg2 - core_npv = core_leg.npv( - disc_curve=_maybe_get_curve_or_dict_maybe_from_solver( + disc_curve = _validate_base_curve( + _maybe_get_curve_maybe_from_solver( self.kwargs.meta["curves"], _curves, f"{core_curve}disc_curve", solver - ), + ) + ) + core_npv: DualTypes = core_leg_.npv( # type: ignore[assignment] + disc_curve=disc_curve, base=self.leg2.settlement_params.currency, fx=fx_, + local=False, ) - nd_disc_curve = _maybe_get_curve_or_dict_maybe_from_solver( - self.kwargs.meta["curves"], _curves, f"{nd_curve}disc_curve", solver + nd_disc_curve = _validate_base_curve( + _maybe_get_curve_maybe_from_solver( + self.kwargs.meta["curves"], _curves, f"{nd_curve}disc_curve", solver + ) ) nd_cf1_npv = self.leg2.periods[0].local_npv(disc_curve=nd_disc_curve, fx=fx_) net_zero_cf = (core_npv + nd_cf1_npv) / nd_disc_curve[ - nd_leg.periods[1].settlement_params.payment + nd_leg_.periods[1].settlement_params.payment ] - required_fx = net_zero_cf / nd_leg.periods[1].settlement_params.notional - original_fx = nd_leg.periods[0].non_deliverable_params.fx_fixing.value_or_forecast(fx=fx_) - return (required_fx - original_fx) * 10000.0 + required_fx = net_zero_cf / nd_leg_.periods[1].settlement_params.notional + original_fx = nd_leg_.periods[0].non_deliverable_params.fx_fixing.value_or_forecast(fx=fx_) # type: ignore[attr-defined] + _: DualTypes = (required_fx - original_fx) * 10000.0 + return _ def npv( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), diff --git a/python/rateslib/instruments/components/ndf.py b/python/rateslib/instruments/components/ndf.py index 985ab295..030feea4 100644 --- a/python/rateslib/instruments/components/ndf.py +++ b/python/rateslib/instruments/components/ndf.py @@ -10,24 +10,26 @@ from rateslib.instruments.components.protocols.kwargs import _KWArgs from rateslib.instruments.components.protocols.pricing import ( _Curves, - _get_fx_maybe_from_solver, + _get_fx_forwards_maybe_from_solver, ) from rateslib.legs.components import CustomLeg from rateslib.periods.components import Cashflow +from rateslib.periods.components.utils import _validate_fx_as_forwards from rateslib.scheduling.frequency import _get_fx_expiry_and_delivery_and_payment if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover Adjuster, + Any, CalInput, - CurveOption_, - Curves_, + CurvesT_, DataFrame, DualTypes, DualTypes_, FXForwards_, FXVolOption_, PeriodFixings, + Sequence, Solver_, _BaseLeg, bool_, @@ -56,6 +58,23 @@ class NDF(_BaseInstrument): ndf = NDF(dt(2025, 1, 5), "usdbrl", fx_rate=5.5, currency="usd") ndf.cashflows() + .. rubric:: Pricing + + The methods of an *NDF* require an :class:`~rateslib.fx.FXForwards` object for ``fx`` . + + They also require a *disc curve*, which is appropriate curve to discount the + cashflows of the deliverable settlement currency. The following input + formats are allowed: + + .. code-block:: python + + curves = disc_curve | [disc_curve] # one curve + curves = [None, disc_curve, None, disc_curve] # four curves + curves = { # dict form is explicit + "disc_curve": disc_curve, + "leg2_disc_curve": disc_curve, + } + .. role:: red .. role:: green @@ -121,8 +140,9 @@ class NDF(_BaseInstrument): The following are **meta parameters**. - curves : XXX - Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. + curves : _BaseCurve, str, dict, _Curves, Sequence, :green:`optional` + Pricing objects passed directly to the *Instrument's* methods' ``curves`` argument. See + **Pricing**. spec: str, :green:`optional` A collective group of parameters. See :ref:`default argument specifications `. @@ -142,15 +162,13 @@ def leg2(self) -> CustomLeg: return self._leg2 @property - def legs(self) -> list[_BaseLeg]: + def legs(self) -> Sequence[_BaseLeg]: """A list of the *Legs* of the *Instrument*.""" return self._legs - def _parse_curves(self, curves: CurveOption_) -> _Curves: + def _parse_curves(self, curves: CurvesT_) -> _Curves: """ - An NDF requires 1 curve to discount curve the settlement currency. - - When given as 2 elements the first is treated as the rate curve and the 2nd as disc curve. + An NDF requires 1 disc curve for the cashflows in the delivery currency. """ if isinstance(curves, NoInput): return _Curves() @@ -163,10 +181,10 @@ def _parse_curves(self, curves: CurveOption_) -> _Curves: ), ) elif isinstance(curves, list | tuple): - if len(curves) == 2: + if len(curves) == 1: return _Curves( disc_curve=curves[0], - leg2_disc_curve=curves[1], + leg2_disc_curve=curves[0], ) elif len(curves) == 4: return _Curves( @@ -177,11 +195,12 @@ def _parse_curves(self, curves: CurveOption_) -> _Curves: raise ValueError( f"{type(self).__name__} requires 1 curve types. Got {len(curves)}." ) - + elif isinstance(curves, _Curves): + return curves else: # `curves` is just a single input which is copied across all curves return _Curves( - disc_curve=curves, - leg2_disc_curve=curves, + disc_curve=curves, # type: ignore[arg-type] + leg2_disc_curve=curves, # type: ignore[arg-type] ) def __init__( @@ -205,14 +224,14 @@ def __init__( reversed: bool = False, leg2_reversed: bool = False, # meta - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), spec: str_ = NoInput(0), ): # determine 'currency' and each 'pair' # this coordinates the allowable combination inputs pair_ = pair.lower() if isinstance(currency, NoInput): - user_args = dict( + user_args: dict[str, Any] = dict( currency=pair_[3:], pair=pair_, fx_fixings=fx_fixings, @@ -341,7 +360,7 @@ def __init__( def cashflows( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -362,7 +381,7 @@ def cashflows( def rate( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), @@ -370,8 +389,8 @@ def rate( settlement: datetime_ = NoInput(0), forward: datetime_ = NoInput(0), metric: str_ = NoInput(0), - ) -> DualTypes_: - fx_ = _get_fx_maybe_from_solver(solver=solver, fx=fx) + ) -> DualTypes: + fx_ = _validate_fx_as_forwards(_get_fx_forwards_maybe_from_solver(solver=solver, fx=fx)) return fx_.rate( pair=self.kwargs.meta["settlement_pair"], settlement=self.kwargs.leg1["settlement"] ) @@ -391,7 +410,7 @@ def _set_pricing_mid( def npv( self, *, - curves: Curves_ = NoInput(0), + curves: CurvesT_ = NoInput(0), solver: Solver_ = NoInput(0), fx: FXForwards_ = NoInput(0), fx_vol: FXVolOption_ = NoInput(0), diff --git a/python/rateslib/instruments/components/sbs.py b/python/rateslib/instruments/components/sbs.py index c9f1cbf0..90395045 100644 --- a/python/rateslib/instruments/components/sbs.py +++ b/python/rateslib/instruments/components/sbs.py @@ -40,7 +40,7 @@ class SBS(_BaseInstrument): """ - An *single currency basis swap (SBS)* composing a :class:`~rateslib.legs.components.FloatLeg` + A *single currency basis swap (SBS)* composing a :class:`~rateslib.legs.components.FloatLeg` and a :class:`~rateslib.legs.components.FloatLeg`. .. rubric:: Examples diff --git a/python/rateslib/legs/components/__init__.py b/python/rateslib/legs/components/__init__.py index ecd465f6..83f6c177 100644 --- a/python/rateslib/legs/components/__init__.py +++ b/python/rateslib/legs/components/__init__.py @@ -6,12 +6,12 @@ __all__ = [ "FixedLeg", - "Amortization", "FloatLeg", - "CreditPremiumLeg", - "CreditProtectionLeg", - "CustomLeg", "ZeroFixedLeg", "ZeroFloatLeg", "ZeroIndexLeg", + "CreditPremiumLeg", + "CreditProtectionLeg", + "CustomLeg", + "Amortization", ] diff --git a/python/rateslib/legs/components/credit.py b/python/rateslib/legs/components/credit.py index 4a8eb920..eb13d563 100644 --- a/python/rateslib/legs/components/credit.py +++ b/python/rateslib/legs/components/credit.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover FX_, + Any, CurveOption_, DualTypes, DualTypes_, @@ -42,6 +43,13 @@ class CreditPremiumLeg(_BaseLeg, _WithExDiv): The schedule object also contains data for payment dates, payment dates for notional exchanges and ex-dividend dates for each period. + .. note:: + + The following are **period parameters** combined with the ``schedule``. + + convention: str, optional + The day count convention applied to calculations of period accrual dates. + See :meth:`~rateslib.scheduling.dcf`. .. note:: The following define generalised **settlement** parameters. @@ -57,11 +65,18 @@ class CreditPremiumLeg(_BaseLeg, _WithExDiv): .. note:: - The following are **period parameters** combined with the ``schedule``. + The following define **rate parameters**. + + fixed_rate: float, Dual, Dual2, Variable, :green:`optional` + The fixed rate of each composited :class:`~rateslib.periods.components.CreditPremiumPeriod`. + + .. note:: + + The following parameters define **credit specific** elements. + + premium_accrued: bool, :green:`optional (set by 'defaults')` + Whether an accrued premium is paid on the event of mid-period credit default. - convention: str, optional - The day count convention applied to calculations of period accrual dates. - See :meth:`~rateslib.scheduling.dcf`. Notes ----- @@ -147,7 +162,7 @@ def __init__( self._regular_periods = tuple( [ - CreditPremiumPeriod( # type: ignore[abstract] + CreditPremiumPeriod( fixed_rate=fixed_rate, premium_accrued=premium_accrued, # currency args @@ -286,7 +301,7 @@ def __init__( self._regular_periods = tuple( [ - CreditProtectionPeriod( # type: ignore[abstract] + CreditProtectionPeriod( # currency args payment=self.schedule.pschedule[i + 1], currency=self._currency, @@ -336,3 +351,6 @@ def analytic_rec_risk( ) ret: float = sum(_) return ret + + def spread(self, *args: Any, **kwargs: Any) -> DualTypes: + raise NotImplementedError(f"{type(self).__name__} does not implement `spread`.") diff --git a/python/rateslib/legs/components/custom.py b/python/rateslib/legs/components/custom.py index 2a64f77b..ae1f255b 100644 --- a/python/rateslib/legs/components/custom.py +++ b/python/rateslib/legs/components/custom.py @@ -7,9 +7,9 @@ if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover - Sequence, Any, DualTypes, + Sequence, ) diff --git a/python/rateslib/legs/components/fixed.py b/python/rateslib/legs/components/fixed.py index 1f6baac3..ec2575d7 100644 --- a/python/rateslib/legs/components/fixed.py +++ b/python/rateslib/legs/components/fixed.py @@ -15,15 +15,15 @@ _BaseLeg, _WithExDiv, ) -from rateslib.periods.components.protocols import ( - _BasePeriod, -) from rateslib.periods.components import ( Cashflow, FixedPeriod, MtmCashflow, ZeroFixedPeriod, ) +from rateslib.periods.components.protocols import ( + _BasePeriod, +) if TYPE_CHECKING: from rateslib.typing import ( # pragma: no cover @@ -533,7 +533,7 @@ def __init__( if not initial_exchange: _ini_cf: Cashflow | None = None else: - _ini_cf = Cashflow( # type: ignore[abstract] + _ini_cf = Cashflow( payment=self.schedule.pschedule2[0], notional=-self._amortization.outstanding[0], currency=self._currency, @@ -555,7 +555,7 @@ def __init__( if not final_exchange_: _final_cf: Cashflow | None = None else: - _final_cf = Cashflow( # type: ignore[abstract] + _final_cf = Cashflow( payment=self.schedule.pschedule2[-1], notional=self._amortization.outstanding[-1], currency=self._currency, @@ -577,7 +577,7 @@ def __init__( self._regular_periods: tuple[FixedPeriod, ...] = tuple( [ - FixedPeriod( # type: ignore[abstract] + FixedPeriod( fixed_rate=fixed_rate, # currency args payment=self.schedule.pschedule[i + 1], @@ -618,7 +618,7 @@ def __init__( # only with notional exchange and some Amortization amount self._amortization_exchange_periods = tuple( [ - Cashflow( # type: ignore[abstract] + Cashflow( notional=self.amortization.amortization[i], payment=self.schedule.pschedule2[i + 1], currency=self._currency, @@ -648,7 +648,7 @@ def __init__( raise ValueError(err.VE_PAIR_AND_LEG_MTM) self._mtm_exchange_periods: tuple[_BasePeriod, ...] | None = tuple( [ - MtmCashflow( # type: ignore[abstract] + MtmCashflow( payment=self.schedule.pschedule2[i + 1], notional=-self.amortization.outstanding[i], pair=pair, @@ -888,7 +888,7 @@ def __init__( if not initial_exchange: _ini_cf: Cashflow | None = None else: - _ini_cf = Cashflow( # type: ignore[abstract] + _ini_cf = Cashflow( payment=self.schedule.pschedule2[0], notional=-self._amortization.outstanding[0], currency=self._currency, @@ -910,7 +910,7 @@ def __init__( if not final_exchange_: _final_cf: Cashflow | None = None else: - _final_cf = Cashflow( # type: ignore[abstract] + _final_cf = Cashflow( payment=self.schedule.pschedule2[-1], notional=self._amortization.outstanding[-1], currency=self._currency, @@ -931,7 +931,7 @@ def __init__( self._exchange_periods = (_ini_cf, _final_cf) self._regular_periods = ( - ZeroFixedPeriod( # type: ignore[abstract] + ZeroFixedPeriod( fixed_rate=NoInput(0), schedule=self.schedule, # currency args @@ -1206,7 +1206,7 @@ def __init__( if not initial_exchange: _ini_cf: Cashflow | None = None else: - _ini_cf = Cashflow( # type: ignore[abstract] + _ini_cf = Cashflow( payment=self.schedule.pschedule2[0], notional=-self._amortization.outstanding[0], currency=self._currency, @@ -1225,7 +1225,7 @@ def __init__( index_only=False, # is only True if there is not final exchange ) final_exchange_ = final_exchange or initial_exchange - _final_cf = Cashflow( # type: ignore[abstract] + _final_cf = Cashflow( payment=self.schedule.pschedule2[-1], notional=self._amortization.outstanding[-1], currency=self._currency, diff --git a/python/rateslib/legs/components/float.py b/python/rateslib/legs/components/float.py index 520bc787..f113d0e3 100644 --- a/python/rateslib/legs/components/float.py +++ b/python/rateslib/legs/components/float.py @@ -29,13 +29,13 @@ IndexMethod, LegFixings, Schedule, + Sequence, _BaseCurve_, _BasePeriod, datetime, datetime_, int_, str_, - Sequence, ) @@ -284,7 +284,7 @@ def __init__( if not initial_exchange: _ini_cf: Cashflow | None = None else: - _ini_cf = Cashflow( # type: ignore[abstract] + _ini_cf = Cashflow( payment=self.schedule.pschedule2[0], notional=-self._amortization.outstanding[0], currency=self._currency, @@ -305,7 +305,7 @@ def __init__( if not final_exchange_: _final_cf: Cashflow | None = None else: - _final_cf = Cashflow( # type: ignore[abstract] + _final_cf = Cashflow( payment=self.schedule.pschedule2[-1], notional=self._amortization.outstanding[-1], currency=self._currency, @@ -339,7 +339,7 @@ def fx_delivery(i: int) -> datetime: rate_fixings_list = _leg_fixings_to_list(rate_fixings, self._schedule.n_periods) self._regular_periods = tuple( [ - FloatPeriod( # type: ignore[abstract] + FloatPeriod( float_spread=float_spread, rate_fixings=rate_fixings_list[i], fixing_method=fixing_method, @@ -384,7 +384,7 @@ def fx_delivery(i: int) -> datetime: else: self._amortization_exchange_periods = tuple( [ - Cashflow( # type: ignore[abstract] + Cashflow( notional=self.amortization.amortization[i], payment=self.schedule.pschedule2[i + 1], currency=self._currency, @@ -413,7 +413,7 @@ def fx_delivery(i: int) -> datetime: raise ValueError(err.VE_PAIR_AND_LEG_MTM) self._mtm_exchange_periods: tuple[MtmCashflow, ...] | None = tuple( [ - MtmCashflow( # type: ignore[abstract] + MtmCashflow( payment=self.schedule.pschedule2[i + 1], notional=-self.amortization.outstanding[i], pair=pair, @@ -747,7 +747,7 @@ def __init__( if not initial_exchange: _ini_cf: Cashflow | None = None else: - _ini_cf = Cashflow( # type: ignore[abstract] + _ini_cf = Cashflow( payment=self.schedule.pschedule2[0], notional=-self._amortization.outstanding[0], currency=self._currency, @@ -768,7 +768,7 @@ def __init__( if not final_exchange_: _final_cf: Cashflow | None = None else: - _final_cf = Cashflow( # type: ignore[abstract] + _final_cf = Cashflow( payment=self.schedule.pschedule2[-1], notional=self._amortization.outstanding[-1], currency=self._currency, @@ -788,7 +788,7 @@ def __init__( self._exchange_periods = (_ini_cf, _final_cf) self._regular_periods = ( - ZeroFloatPeriod( # type: ignore[abstract] + ZeroFloatPeriod( float_spread=float_spread, rate_fixings=rate_fixings, fixing_method=fixing_method, diff --git a/python/rateslib/legs/components/protocols/analytic_delta.py b/python/rateslib/legs/components/protocols/analytic_delta.py index d2c4fac6..8e072155 100644 --- a/python/rateslib/legs/components/protocols/analytic_delta.py +++ b/python/rateslib/legs/components/protocols/analytic_delta.py @@ -13,11 +13,11 @@ DualTypes, FXForwards_, FXVolOption_, + Sequence, _BaseCurve_, _BasePeriod, datetime_, str_, - Sequence, ) diff --git a/python/rateslib/legs/components/protocols/analytic_fixings.py b/python/rateslib/legs/components/protocols/analytic_fixings.py index 3c072a48..79b53e09 100644 --- a/python/rateslib/legs/components/protocols/analytic_fixings.py +++ b/python/rateslib/legs/components/protocols/analytic_fixings.py @@ -12,10 +12,10 @@ CurveOption_, FXForwards_, FXVolOption_, + Sequence, _BaseCurve_, _BasePeriod, datetime_, - Sequence, ) diff --git a/python/rateslib/legs/components/protocols/cashflows.py b/python/rateslib/legs/components/protocols/cashflows.py index 6b62f1c2..37c261e2 100644 --- a/python/rateslib/legs/components/protocols/cashflows.py +++ b/python/rateslib/legs/components/protocols/cashflows.py @@ -13,12 +13,12 @@ FXForwards_, FXVolOption_, Schedule, + Sequence, _BaseCurve_, _BasePeriod, datetime, datetime_, str_, - Sequence, ) diff --git a/python/rateslib/legs/components/protocols/npv.py b/python/rateslib/legs/components/protocols/npv.py index a29810d1..4acb62d8 100644 --- a/python/rateslib/legs/components/protocols/npv.py +++ b/python/rateslib/legs/components/protocols/npv.py @@ -14,11 +14,11 @@ DualTypes, FXForwards_, FXVolOption_, + Sequence, _BaseCurve_, _BasePeriod, datetime_, str_, - Sequence, ) diff --git a/python/rateslib/periods/components/__init__.py b/python/rateslib/periods/components/__init__.py index ec2abb83..b0e0c638 100644 --- a/python/rateslib/periods/components/__init__.py +++ b/python/rateslib/periods/components/__init__.py @@ -23,7 +23,6 @@ ZeroFloatPeriod, ) from rateslib.periods.components.fx_volatility import FXCallPeriod, FXOptionPeriod, FXPutPeriod -from rateslib.periods.components.protocols import _BasePeriod, _BasePeriodStatic __all__ = [ "Cashflow", @@ -39,11 +38,11 @@ # "IndexFloatPeriod", # "NonDeliverableFloatPeriod", # "NonDeliverableIndexFloatPeriod", + "ZeroFixedPeriod", + "ZeroFloatPeriod", + "CreditPremiumPeriod", + "CreditProtectionPeriod", "FXOptionPeriod", "FXCallPeriod", "FXPutPeriod", - "CreditPremiumPeriod", - "CreditProtectionPeriod", - "ZeroFixedPeriod", - "ZeroFloatPeriod", ] diff --git a/python/rateslib/periods/components/float_period.py b/python/rateslib/periods/components/float_period.py index 9a74f6a1..3b4aa7e9 100644 --- a/python/rateslib/periods/components/float_period.py +++ b/python/rateslib/periods/components/float_period.py @@ -780,7 +780,7 @@ def __init__( ) rate_fixings_ = _leg_fixings_to_list(rate_fixings, self.schedule.n_periods) self._float_periods: list[FloatPeriod] = [ - FloatPeriod( # type: ignore[abstract] + FloatPeriod( float_spread=float_spread, rate_fixings=rate_fixings_[i], fixing_method=fixing_method, diff --git a/python/rateslib/periods/components/fx_volatility.py b/python/rateslib/periods/components/fx_volatility.py index d5c0d77c..33a60b59 100644 --- a/python/rateslib/periods/components/fx_volatility.py +++ b/python/rateslib/periods/components/fx_volatility.py @@ -48,7 +48,7 @@ _NonDeliverableParams, _SettlementParams, ) -from rateslib.periods.components.protocols import _BasePeriodStatic +from rateslib.periods.components.protocols import _BasePeriodStatic, _WithAnalyticFXOptionGreeks from rateslib.periods.components.utils import ( _get_vol_delta_type, _get_vol_maybe_from_obj, @@ -79,7 +79,7 @@ ) -class FXOptionPeriod(_BasePeriodStatic, metaclass=ABCMeta): +class FXOptionPeriod(_BasePeriodStatic, _WithAnalyticFXOptionGreeks, metaclass=ABCMeta): r""" An abstract base class for implementing FX option *Periods*. @@ -225,7 +225,7 @@ def rate_params(self) -> None: return self._rate_params @property - def fx_option_params(self) -> _FXOptionParams: # type: ignore[override] + def fx_option_params(self) -> _FXOptionParams: """The :class:`~rateslib.periods.components.parameters._FXOptionParams` of the *Period*.""" return self._fx_option_params diff --git a/python/rateslib/periods/components/protocols/__init__.py b/python/rateslib/periods/components/protocols/__init__.py index 83b12af1..b2f20501 100644 --- a/python/rateslib/periods/components/protocols/__init__.py +++ b/python/rateslib/periods/components/protocols/__init__.py @@ -27,7 +27,6 @@ class _BasePeriod( _WithCashflows, _WithAnalyticDelta, _WithAnalyticRateFixings, - _WithAnalyticFXOptionGreeks, metaclass=ABCMeta, ): """Abstract base class for *Period* types.""" diff --git a/python/rateslib/periods/components/protocols/analytic_greeks.py b/python/rateslib/periods/components/protocols/analytic_greeks.py index 3dad090f..9b253483 100644 --- a/python/rateslib/periods/components/protocols/analytic_greeks.py +++ b/python/rateslib/periods/components/protocols/analytic_greeks.py @@ -29,7 +29,8 @@ class _WithAnalyticFXOptionGreeks(Protocol): - fx_option_params: _FXOptionParams + @property + def fx_option_params(self) -> _FXOptionParams: ... @property def settlement_params(self) -> _SettlementParams: ... diff --git a/python/rateslib/scheduling/__init__.py b/python/rateslib/scheduling/__init__.py index 2a0453de..afeadc7b 100644 --- a/python/rateslib/scheduling/__init__.py +++ b/python/rateslib/scheduling/__init__.py @@ -150,18 +150,18 @@ __all__ = ( "Schedule", - "add_tenor", + "Cal", + "NamedCal", + "UnionCal", "Adjuster", - "Frequency", - "StubInference", "Convention", + "Frequency", "Imm", - "Cal", - "dcf", - "NamedCal", "RollDay", - "UnionCal", + "StubInference", + "add_tenor", "get_calendar", "get_imm", "next_imm", + "dcf", )