Skip to content

Commit

Permalink
REF: rename BaseMixin to Metrics and contain cashflows_table (#678
Browse files Browse the repository at this point in the history
)

Co-authored-by: JHM Darbyshire (M1) <[email protected]>
  • Loading branch information
attack68 and attack68 authored Feb 7, 2025
1 parent 9f2b9e6 commit 34600a5
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 68 deletions.
13 changes: 13 additions & 0 deletions docs/source/i_whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ and this can be given on the public **Issues** board at the project github
repository: `Rateslib Project <https://github.com/attack68/rateslib>`_, or by direct
email contact, see `rateslib <https://rateslib.com>`_.

1.8.0 (No release date)
****************************

.. list-table::
:widths: 25 75
:header-rows: 1

* - Feature
- Description
* - Refactor
- Rename :class:`~rateslib.instruments.BaseMixin` to :class:`~rateslib.instruments.Metrics`.
(`678 <https://github.com/attack68/rateslib/pull/678>`_)

1.7.0 (31st January 2025)
****************************

Expand Down
4 changes: 2 additions & 2 deletions python/rateslib/instruments/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from rateslib.instruments.base import BaseDerivative, BaseMixin
from rateslib.instruments.base import BaseDerivative, Metrics
from rateslib.instruments.bonds import (
Bill,
BillCalcMode,
Expand Down Expand Up @@ -44,7 +44,7 @@

__all__ = [
"BaseDerivative",
"BaseMixin",
"Metrics",
"Bill",
"BondMixin",
"BondCalcMode",
Expand Down
61 changes: 59 additions & 2 deletions python/rateslib/instruments/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
from rateslib.typing import FX_, NPV, Any, CalInput, Curves_, DualTypes, Leg, Solver_, str_


class BaseMixin:
class Metrics:
"""
Base class for *Instruments* adding optional pricing parameters, such as fixed rates,
float spreads etc. Also provides key pricing methods.
"""

_fixed_rate_mixin: bool = False
_float_spread_mixin: bool = False
_leg2_fixed_rate_mixin: bool = False
Expand Down Expand Up @@ -380,8 +385,60 @@ def rate(self, *args: Any, **kwargs: Any) -> DualTypes:
def __repr__(self) -> str:
return f"<rl.{type(self).__name__} at {hex(id(self))}>"

def cashflows_table(
self,
curves: Curves_ = NoInput(0),
solver: Solver | NoInput = NoInput(0),
fx: FX_ = NoInput(0),
base: str | NoInput = NoInput(0),
**kwargs: Any,
) -> DataFrame:
"""
Aggregate the values derived from a :meth:`~rateslib.instruments.BaseMixin.cashflows`
method on an *Instrument*.
Parameters
----------
curves : CurveType, str or list of such, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
solver : Solver, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
fx : float, FXRates, FXForwards, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
base : str, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
kwargs : dict
Additional arguments input the underlying ``cashflows`` method of the *Instrument*.
Returns
-------
DataFrame
"""
cashflows = self.cashflows(curves, solver, fx, base, **kwargs)
cashflows = cashflows[
[
defaults.headers["currency"],
defaults.headers["collateral"],
defaults.headers["payment"],
defaults.headers["cashflow"],
]
]
_: DataFrame = cashflows.groupby( # type: ignore[assignment]
[
defaults.headers["currency"],
defaults.headers["collateral"],
defaults.headers["payment"],
],
dropna=False,
)
_ = _.sum().unstack([0, 1]).droplevel(0, axis=1) # type: ignore[arg-type]
_.columns.names = ["local_ccy", "collateral_ccy"]
_.index.names = ["payment"]
_ = _.sort_index(ascending=True, axis=0).infer_objects().fillna(0.0)
return _


class BaseDerivative(Sensitivities, BaseMixin, metaclass=ABCMeta):
class BaseDerivative(Sensitivities, Metrics, metaclass=ABCMeta):
"""
Abstract base class with common parameters for many *Derivative* subclasses.
Expand Down
6 changes: 3 additions & 3 deletions python/rateslib/instruments/bonds/securities.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from rateslib.default import NoInput, _drb
from rateslib.dual import Dual, Dual2, gradient, newton_1dim, quadratic_eqn
from rateslib.dual.utils import _dual_float, _get_order_of
from rateslib.instruments.base import BaseMixin
from rateslib.instruments.base import Metrics
from rateslib.instruments.bonds.conventions import (
BILL_MODE_MAP,
BOND_MODE_MAP,
Expand Down Expand Up @@ -951,7 +951,7 @@ def root(z: DualTypes, P_tgt: DualTypes) -> tuple[DualTypes, float]:
return _


class FixedRateBond(Sensitivities, BondMixin, BaseMixin): # type: ignore[misc]
class FixedRateBond(Sensitivities, BondMixin, Metrics): # type: ignore[misc]
# TODO (mid) ensure calculations work for amortizing bonds.
"""
Create a fixed rate bond security.
Expand Down Expand Up @@ -2472,7 +2472,7 @@ def duration(self, *args: Any, **kwargs: Any) -> float:
# Contact rateslib at gmail.com if this code is observed outside its intended sphere.


class FloatRateNote(Sensitivities, BondMixin, BaseMixin): # type: ignore[misc]
class FloatRateNote(Sensitivities, BondMixin, Metrics): # type: ignore[misc]
"""
Create a floating rate note (FRN) security.
Expand Down
11 changes: 10 additions & 1 deletion python/rateslib/instruments/fx_volatility/vanilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from rateslib.default import NoInput, PlotOutput, _drb, plot
from rateslib.dual.utils import _dual_float
from rateslib.fx_volatility import FXVolObj
from rateslib.instruments.base import Metrics
from rateslib.instruments.sensitivities import Sensitivities
from rateslib.instruments.utils import (
_get_fxvol_curves_fx_and_base_maybe_from_solver,
Expand All @@ -24,6 +25,8 @@
from rateslib.periods.utils import _validate_fx_as_forwards

if TYPE_CHECKING:
from typing import NoReturn

import numpy as np

from rateslib.typing import (
Expand Down Expand Up @@ -56,7 +59,7 @@ class _PricingMetrics:
f_d: DualTypes


class FXOption(Sensitivities, metaclass=ABCMeta):
class FXOption(Sensitivities, Metrics, metaclass=ABCMeta):
"""
Create an *FX Option*.
Expand Down Expand Up @@ -655,6 +658,12 @@ def analytic_greeks(
premium=NoInput(0),
)

def analytic_delta(self, *args: Any, leg: int = 1, **kwargs: Any) -> NoReturn:
"""Not implemented for Option types.
Use :class:`~rateslib.instruments.FXOption.analytic_greeks`.
"""
raise NotImplementedError("For Option types use `analytic_greeks`.")

def _plot_payoff(
self,
window: list[float] | NoInput = NoInput(0),
Expand Down
24 changes: 20 additions & 4 deletions python/rateslib/instruments/generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from rateslib.default import NoInput, _drb
from rateslib.dual import dual_log
from rateslib.fx_volatility import FXDeltaVolSmile, FXDeltaVolSurface
from rateslib.instruments.base import BaseMixin
from rateslib.instruments.base import Metrics
from rateslib.instruments.sensitivities import Sensitivities
from rateslib.instruments.utils import (
_composit_fixings_table,
Expand Down Expand Up @@ -44,7 +44,7 @@
# Contact info at rateslib.com if this code is observed outside its intended sphere of use.


class Value(BaseMixin):
class Value(Metrics):
"""
A null *Instrument* which can be used within a :class:`~rateslib.solver.Solver`
to directly parametrise a *Curve* node, via some calculated value.
Expand Down Expand Up @@ -155,7 +155,7 @@ def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("`Value` instrument has no concept of analytic delta.")


class VolValue(BaseMixin):
class VolValue(Metrics):
"""
A null *Instrument* which can be used within a :class:`~rateslib.solver.Solver`
to directly parametrise a *Vol* node, via some calculated metric.
Expand Down Expand Up @@ -618,7 +618,7 @@ def _instrument_npv(
return instrument.npv(*args, **kwargs)


class Portfolio(Sensitivities):
class Portfolio(Sensitivities, Metrics):
"""
Create a collection of *Instruments* to group metrics
Expand Down Expand Up @@ -762,6 +762,16 @@ def gamma(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
return super().gamma(*args, **kwargs)

def exo_delta(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
Calculate the delta of the *Instrument* measured
against user defined :class:`~rateslib.dual.Variable`.
For arguments see
:meth:`Sensitivities.exo_delta()<rateslib.instruments.Sensitivities.exo_delta>`.
"""
return super().exo_delta(*args, **kwargs)

def fixings_table(
self,
curves: Curves_ = NoInput(0),
Expand Down Expand Up @@ -798,3 +808,9 @@ def fixings_table(
continue
df_result = _composit_fixings_table(df_result, df)
return df_result

def rate(self, *args: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("`rate` is not defined for Portfolio.")

def analytic_delta(self, *args: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("`analytic_delta` is not defined for Portfolio.")
6 changes: 3 additions & 3 deletions python/rateslib/instruments/rates/multi_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from rateslib.dual import Dual, Dual2, Variable
from rateslib.dual.utils import _dual_float
from rateslib.fx import FXForwards, FXRates, forward_fx
from rateslib.instruments.base import BaseDerivative, BaseMixin
from rateslib.instruments.base import BaseDerivative, Metrics
from rateslib.instruments.sensitivities import Sensitivities
from rateslib.instruments.utils import (
_composit_fixings_table,
Expand Down Expand Up @@ -59,7 +59,7 @@
)


class FXExchange(Sensitivities, BaseMixin):
class FXExchange(Sensitivities, Metrics):
"""
Create a simple exchange of two currencies.
Expand Down Expand Up @@ -271,7 +271,7 @@ def analytic_delta(self, *args: Any, **kwargs: Any) -> DualTypes:
raise NotImplementedError("`analytic_delta` for FXExchange not defined.")


class NDF(Sensitivities):
class NDF(Sensitivities, Metrics):
"""
Create a non-deliverable forward (NDF).
Expand Down
53 changes: 0 additions & 53 deletions python/rateslib/instruments/sensitivities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from pandas import DataFrame

from rateslib import defaults
from rateslib.default import NoInput
from rateslib.fx import FXForwards, FXRates
from rateslib.instruments.utils import (
Expand Down Expand Up @@ -236,55 +235,3 @@ def gamma(
solver._set_ad_order(_ad1)

return grad_s_sT_P

def cashflows_table(
self,
curves: Curves_ = NoInput(0),
solver: Solver | NoInput = NoInput(0),
fx: FX_ = NoInput(0),
base: str | NoInput = NoInput(0),
**kwargs: Any,
) -> DataFrame:
"""
Aggregate the values derived from a :meth:`~rateslib.instruments.BaseMixin.cashflows`
method on an *Instrument*.
Parameters
----------
curves : CurveType, str or list of such, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
solver : Solver, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
fx : float, FXRates, FXForwards, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
base : str, optional
Argument input to the underlying ``cashflows`` method of the *Instrument*.
kwargs : dict
Additional arguments input the underlying ``cashflows`` method of the *Instrument*.
Returns
-------
DataFrame
"""
cashflows = self.cashflows(curves, solver, fx, base, **kwargs)
cashflows = cashflows[
[
defaults.headers["currency"],
defaults.headers["collateral"],
defaults.headers["payment"],
defaults.headers["cashflow"],
]
]
_: DataFrame = cashflows.groupby( # type: ignore[assignment]
[
defaults.headers["currency"],
defaults.headers["collateral"],
defaults.headers["payment"],
],
dropna=False,
)
_ = _.sum().unstack([0, 1]).droplevel(0, axis=1) # type: ignore[arg-type]
_.columns.names = ["local_ccy", "collateral_ccy"]
_.index.names = ["payment"]
_ = _.sort_index(ascending=True, axis=0).infer_objects().fillna(0.0)
return _

0 comments on commit 34600a5

Please sign in to comment.