Skip to content

Commit 52beb64

Browse files
authored
REF: AD-safe analytic_rec_risk for CreditProtectionPeriod (#247) (#1178)
1 parent 6e440d3 commit 52beb64

File tree

3 files changed

+56
-34
lines changed

3 files changed

+56
-34
lines changed

python/rateslib/legs/credit.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
if TYPE_CHECKING:
2424
from rateslib.typing import ( # pragma: no cover
25-
FX_,
2625
Any,
2726
CurveOption_,
2827
DualTypes,
@@ -342,7 +341,7 @@ def analytic_rec_risk(
342341
self,
343342
rate_curve: _BaseCurve_ = NoInput(0),
344343
disc_curve: _BaseCurve_ = NoInput(0),
345-
fx: FX_ = NoInput(0),
344+
fx: FXForwards_ = NoInput(0),
346345
base: str_ = NoInput(0),
347346
) -> float:
348347
"""

python/rateslib/periods/credit.py

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111

1212
from __future__ import annotations
1313

14-
from dataclasses import replace
1514
from datetime import timedelta
1615
from typing import TYPE_CHECKING
1716

1817
import rateslib.errors as err
1918
from rateslib import defaults
20-
from rateslib.dual import Variable, gradient
2119
from rateslib.dual.utils import _dual_float
2220
from rateslib.enums.generics import Err, NoInput, Ok, Result, _drb
2321
from rateslib.periods.parameters import (
@@ -27,15 +25,15 @@
2725
_SettlementParams,
2826
)
2927
from 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
3130
from rateslib.scheduling import Frequency, get_calendar
3231
from rateslib.scheduling.adjuster import _get_adjuster
3332
from rateslib.scheduling.convention import _get_convention
3433
from rateslib.scheduling.frequency import _get_frequency
3534

3635
if 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

requirements-minimum.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ matplotlib==3.5.1
66

77
# run tests in github actions
88
maturin>=1.6.0,<2.0
9-
pytest>=8.2.2,<9.0
9+
pytest>=9.0,<10.0

0 commit comments

Comments
 (0)