Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/i_whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ Some themes for this release involved:
(`854 <https://github.com/attack68/rateslib/pull/854>`_)
(`855 <https://github.com/attack68/rateslib/pull/855>`_)
(`873 <https://github.com/attack68/rateslib/pull/873>`_)
- :red:`Minor Breaking Change!` Many **attributes** of a :class:`~rateslib.curves.ProxyCurve`
have been restructured into a :class:`~rateslib.curves.utils._ProxyCurveInterpolator`
class, to be consistent with the other attribute changes on *Curves*.
(`900 <https://github.com/attack68/rateslib/pull/900>`_)
- :red:`Major Breaking Change!` The **attributes** on *FXVol* pricing objects are
reorganised into ``attributes``.
(`867 <https://github.com/attack68/rateslib/pull/867>`_)
Expand Down
67 changes: 44 additions & 23 deletions python/rateslib/curves/curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
from rateslib.calendars import add_tenor, dcf
from rateslib.calendars.rs import get_calendar
from rateslib.curves.interpolation import InterpolationFunction
from rateslib.curves.utils import _CurveInterpolator, _CurveMeta, _CurveNodes, _CurveType
from rateslib.curves.utils import (
_CurveInterpolator,
_CurveMeta,
_CurveNodes,
_CurveType,
_ProxyCurveInterpolator,
)
from rateslib.default import (
NoInput,
PlotOutput,
Expand Down Expand Up @@ -2843,6 +2849,9 @@ class ProxyCurve(Curve):
"""

_base_type = _CurveType.dfs
_interpolator: _ProxyCurveInterpolator # type: ignore[assignment]
_nodes: _CurveNodes
_meta: _CurveMeta

def __init__(
self,
Expand All @@ -2855,46 +2864,58 @@ def __init__(
id: str_ = NoInput(0), # noqa: A002
):
self._id = _drb(uuid4().hex[:5], id) # 1 in a million clash
cash_ccy, coll_ccy = cashflow.lower(), collateral.lower()
self.fx_forwards = fx_forwards
self.cash_currency = cash_ccy
self.cash_pair = f"{cash_ccy}{cash_ccy}"
self.cash_idx = self.fx_forwards.currencies[cash_ccy]
self.coll_currency = coll_ccy
self.coll_pair = f"{coll_ccy}{coll_ccy}"
self.coll_idx = self.fx_forwards.currencies[coll_ccy]
self.pair = f"{cash_ccy}{coll_ccy}"
self.terminal = self.fx_forwards.fx_curves[self.cash_pair].nodes.final

self._interpolator = _ProxyCurveInterpolator(
_fx_forwards=fx_forwards, _cash=cashflow.lower(), _collateral=collateral.lower()
)

self._meta = _CurveMeta(
get_calendar(_drb(self.fx_forwards.fx_curves[self.cash_pair].meta.calendar, calendar)),
_drb(self.fx_forwards.fx_curves[self.cash_pair].meta.convention, convention).lower(),
_drb(self.fx_forwards.fx_curves[self.cash_pair].meta.modifier, modifier).upper(),
get_calendar(
_drb(fx_forwards.fx_curves[self.interpolator.cash_pair].meta.calendar, calendar)
),
_drb(
fx_forwards.fx_curves[self.interpolator.cash_pair].meta.convention, convention
).lower(),
_drb(
fx_forwards.fx_curves[self.interpolator.cash_pair].meta.modifier, modifier
).upper(),
NoInput(0), # index meta not relevant for ProxyCurve
0,
coll_ccy,
self.interpolator.collateral,
100, # credit elements irrelevant for a PxyCv
1.0, # credit elements irrelevant for a PxyCv
)
# CurveNodes attached for date attribution
self._nodes = _CurveNodes({self.fx_forwards.immediate: 0.0, self.terminal: 0.0})
self._nodes = _CurveNodes(
{
fx_forwards.immediate: 0.0,
fx_forwards.fx_curves[self.interpolator.cash_pair].nodes.final: 0.0,
}
)

@property
def _ad(self) -> int: # type: ignore[override]
return self.fx_forwards._ad
return self.interpolator.fx_forwards._ad

@property
def interpolator(self) -> _ProxyCurveInterpolator: # type: ignore[override]
"""An instance of :class:`~rateslib.curves.utils._ProxyCurveInterpolator`."""
return self._interpolator

@property
def _state(self) -> int: # type: ignore[override]
# ProxyCurve is directly associated with its FXForwards object
self.fx_forwards._validate_state()
return self.fx_forwards._state
self.interpolator.fx_forwards._validate_state()
return self.interpolator.fx_forwards._state

def __getitem__(self, date: datetime) -> DualTypes:
_1: DualTypes = self.fx_forwards.rate(self.pair, date)
_2: DualTypes = self.fx_forwards.fx_rates_immediate._fx_array_el(
self.cash_idx, self.coll_idx
_1: DualTypes = self.interpolator.fx_forwards.rate(self.interpolator.pair, date)
_2: DualTypes = self.interpolator.fx_forwards.fx_rates_immediate._fx_array_el(
self.interpolator.cash_index, self.interpolator.collateral_index
)
_3: DualTypes = self.fx_forwards.fx_curves[self.coll_pair][date]
_3: DualTypes = self.interpolator.fx_forwards.fx_curves[self.interpolator.collateral_pair][
date
]
return _1 / _2 * _3

# Not Implemented
Expand Down
63 changes: 62 additions & 1 deletion python/rateslib/curves/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
from rateslib.splines import PPSplineDual, PPSplineDual2, PPSplineF64

if TYPE_CHECKING:
from rateslib.typing import Any, CalTypes, DualTypes, Variable, float_, str_ # pragma: no cover
from rateslib.typing import (
Any,
CalTypes,
DualTypes,
FXForwards,
Variable,
float_,
str_,
) # pragma: no cover


class _CurveType(Enum):
Expand Down Expand Up @@ -404,6 +412,59 @@ def _from_json(cls, loaded_json: dict[str, Any]) -> _CurveInterpolator:
)


@dataclass(frozen=True)
class _ProxyCurveInterpolator:
"""
A container for data relating to deriving the DFs of a :class:`~rateslib.curves.ProxyCurve`
from other :class:`~rateslib.curves.Curve` objects and :class:`~rateslib.fx.FXForwards`.
"""

_fx_forwards: FXForwards
_cash: str
_collateral: str

@property
def fx_forwards(self) -> FXForwards:
"""The :class:`~rateslib.fx.FXForwards` object containing :class:`~rateslib.fx.FXRates`
and :class:`~rateslib.curves.Curve` objects."""
return self._fx_forwards

@property
def cash(self) -> str:
"""The currency of the cashflows."""
return self._cash

@property
def collateral(self) -> str:
"""The currency of the collateral assuming PAI."""
return self._collateral

@property
def pair(self) -> str:
"""A pair of currencies representing the cashflow and collateral."""
return self.cash + self.collateral

@property
def cash_index(self) -> int:
"""The index of the cash currency in the :class:`~rateslib.fx.FXForwards` object."""
return self.fx_forwards.currencies[self.cash]

@property
def collateral_index(self) -> int:
"""The index of the collateral currency in the :class:`~rateslib.fx.FXForwards` object."""
return self.fx_forwards.currencies[self.collateral]

@property
def cash_pair(self) -> str:
"""A pair constructed from the cash currency"""
return self.cash + self.cash

@property
def collateral_pair(self) -> str:
"""A pair constructed from the collateral currency"""
return self.collateral + self.collateral


@dataclass(frozen=True)
class _CurveNodes:
"""
Expand Down