1111
1212from __future__ import annotations
1313
14- from dataclasses import replace
1514from datetime import timedelta
1615from typing import TYPE_CHECKING
1716
1817import rateslib .errors as err
1918from rateslib import defaults
20- from rateslib .dual import Variable , gradient
2119from rateslib .dual .utils import _dual_float
2220from rateslib .enums .generics import Err , NoInput , Ok , Result , _drb
2321from rateslib .periods .parameters import (
2725 _SettlementParams ,
2826)
2927from rateslib .periods .protocols import _BasePeriod
30- from rateslib .periods .utils import _try_validate_base_curve , _validate_credit_curves
28+ from rateslib .periods .protocols .npv import _screen_ex_div_and_forward
29+ from rateslib .periods .utils import _maybe_local , _try_validate_base_curve , _validate_credit_curves
3130from rateslib .scheduling import Frequency , get_calendar
3231from rateslib .scheduling .adjuster import _get_adjuster
3332from rateslib .scheduling .convention import _get_convention
3433from rateslib .scheduling .frequency import _get_frequency
3534
3635if TYPE_CHECKING : # pragma: no cover
3736 from rateslib .typing import (
38- FX_ ,
3937 Adjuster ,
4038 CalInput ,
4139 CurveOption_ ,
@@ -550,7 +548,16 @@ def immediate_local_npv(
550548 fx_vol : _FXVolOption_ = NoInput (0 ),
551549 ) -> DualTypes :
552550 rate_curve_ , disc_curve_ = _validate_credit_curves (rate_curve , disc_curve ).unwrap ()
551+ quadrature = self ._quadrature (rate_curve_ , disc_curve_ )
552+ cf = self .cashflow (rate_curve = rate_curve )
553+ return quadrature * cf
553554
555+ def _quadrature (
556+ self ,
557+ rate_curve_ : _BaseCurve ,
558+ disc_curve_ : _BaseCurve ,
559+ ) -> DualTypes :
560+ """determine the integral component of the NPV function using discretised intervals"""
554561 discretization = rate_curve_ .meta .credit_discretization
555562
556563 if self .period_params .start < rate_curve_ .nodes .initial :
@@ -570,9 +577,7 @@ def immediate_local_npv(
570577 value += 0.5 * (v1 + v2 ) * (q1 - q2 )
571578 # value += v2 * (q1 - q2)
572579
573- # curves are pre-validated so will not error
574- cf = self .cashflow (rate_curve = rate_curve )
575- return value * cf
580+ return value
576581
577582 def try_immediate_local_analytic_delta (
578583 self ,
@@ -589,38 +594,56 @@ def analytic_rec_risk(
589594 self ,
590595 rate_curve : _BaseCurve_ = NoInput (0 ),
591596 disc_curve : _BaseCurve_ = NoInput (0 ),
592- fx : FX_ = NoInput (0 ),
597+ fx : FXForwards_ = NoInput (0 ),
593598 base : str_ = NoInput (0 ),
594- ) -> float :
599+ settlement : datetime_ = NoInput (0 ),
600+ forward : datetime_ = NoInput (0 ),
601+ ) -> DualTypes :
595602 """
596603 Calculate the exposure of the NPV to a change in recovery rate.
597604
598- For parameters see
599- :meth:`BasePeriod.analytic_delta()<rateslib.periods.BasePeriod.analytic_delta>`
605+ .. role:: red
606+
607+ .. role:: green
608+
609+ Parameters
610+ ----------
611+ rate_curve: _BaseCurve, :red:`required`
612+ Used to forecast credit parameters, such as hazard rates and recovery rates.
613+ disc_curve: _BaseCurve, :red:`required`
614+ Used to discount cashflows.
615+ fx: FXForwards, :green:`optional`
616+ The :class:`~rateslib.fx.FXForwards` object used for currency conversion.
617+ base: str, :green:`optional`
618+ The currency to convert the *local settlement* value into.
619+ settlement: datetime, :green:`optional`
620+ The assumed settlement date of the *PV* determination. Used only to evaluate
621+ *ex-dividend* status.
622+ forward: datetime, :green:`optional`
623+ The future date to project the *PV* to using the ``disc_curve``.
600624
601625 Returns
602626 -------
603- float
627+ float, Dual, Dual2
604628 """
605- c_res = _validate_credit_curves (rate_curve , disc_curve )
606- if isinstance (c_res , Err ):
607- c_res .unwrap ()
608- else :
609- rate_curve_ , disc_curve_ = c_res .unwrap ()
610-
611- haz_curve = rate_curve_ .copy ()
612- haz_curve ._meta = replace ( # type: ignore[misc]
613- rate_curve_ .meta ,
614- _credit_recovery_rate = Variable (
615- _dual_float (rate_curve_ .meta .credit_recovery_rate ), ["__rec_rate__" ], []
616- ),
629+ rate_curve_ , disc_curve_ = _validate_credit_curves (rate_curve , disc_curve ).unwrap ()
630+ quadrature = self ._quadrature (rate_curve_ , disc_curve_ )
631+ local_immediate_value = quadrature * self .settlement_params .notional * 0.01
632+
633+ local_value = _screen_ex_div_and_forward (
634+ local_value = Ok (local_immediate_value ),
635+ rate_curve = rate_curve ,
636+ disc_curve = disc_curve ,
637+ ex_dividend = self .settlement_params .ex_dividend ,
638+ settlement = settlement ,
639+ forward = forward ,
617640 )
618- pv : DualTypes = self .npv ( # type: ignore[assignment]
619- rate_curve = haz_curve ,
620- disc_curve = disc_curve_ ,
621- fx = fx , # type: ignore[arg-type]
622- base = base ,
641+ ret : DualTypes = _maybe_local ( # type: ignore[assignment] # local is False
642+ value = local_value .unwrap (),
623643 local = False ,
644+ currency = self .settlement_params .currency ,
645+ fx = fx ,
646+ base = base ,
647+ forward = forward ,
624648 )
625- _ : float = _dual_float (gradient (pv , ["__rec_rate__" ], order = 1 )[0 ])
626- return _ * 0.01
649+ return ret
0 commit comments