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
12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,19 @@ exclude = [
"/instruments/components/fx_options",
# "/instruments/components/protocols",
# "/instruments/components/cds.py",
"/instruments/components/fly.py",
# "/instruments/components/fly.py",
# "/instruments/components/fra.py",
# "/instruments/components/fx_forward.py",
# "/instruments/components/fx_swap.py",
"/instruments/components/fx_vol_value.py",
# "/instruments/components/fx_vol_value.py",
# "/instruments/components/iirs.py",
# "/instruments/components/irs.py",
# "/instruments/components/ndf.py",
"/instruments/components/portfolio.py",
# "/instruments/components/portfolio.py",
# "/instruments/components/sbs.py",
"/instruments/components/spread.py",
"/instruments/components/stir_future.py",
"/instruments/components/value.py",
# "/instruments/components/spread.py",
# "/instruments/components/stir_future.py",
# "/instruments/components/value.py",
# "/instruments/components/xcs.py",
# "/instruments/components/zcis.py",
# "/instruments/components/zcs.py",
Expand Down
119 changes: 47 additions & 72 deletions python/rateslib/instruments/components/fly.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
if TYPE_CHECKING:
from rateslib.typing import (
Any,
Curves_,
CurvesT_,
DualTypes,
FXForwards_,
FXVolOption_,
Expand Down Expand Up @@ -64,16 +64,54 @@ def _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:

class Fly(_BaseInstrument):
"""
Create a butterfly of *Instruments*.
A *Butterfly* of :class:`~rateslib.instruments.components.protocols._BaseInstrument`.

.. rubric:: Examples

The following initialises a *Butterfly* of *IRSs*.

.. ipython:: python
:suppress:

from rateslib.instruments.components import Fly, IRS
from datetime import datetime as dt

.. ipython:: python

fly = Fly(
instrument1=IRS(dt(2000, 1, 1), "1y", notional=10e6, spec="eur_irs", curves=["estr"]),
instrument2=IRS(dt(2000, 1, 1), "2y", notional=-5e6, spec="eur_irs", curves=["estr"]),
instrument3=IRS(dt(2000, 1, 1), "3y", notional=1.75e6, spec="eur_irs", curves=["estr"]),
)
fly.cashflows()

.. rubric:: Pricing

Each :class:`~rateslib.instruments.components.protocols._BaseInstrument` should have
its own ``curves`` and ``vol`` objects set at its initialisation, according to the
documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,
these can be universally passed to each *Instrument* but in many cases that would be
technically impossible since each *Instrument* might require difference pricing objects, e.g.
if the *Instruments* have difference currencies. For a *Fly*
of three *IRS* in the same currency this would be possible, however.

Parameters
----------
instrument1 : _BaseInstrument
An *Instrument* with the shortest maturity.
The *Instrument* with the shortest maturity.
instrument2 : _BaseInstrument
The *Instrument* of the body of the *Fly*.
The *Instrument* with the intermediate maturity.
instrument3 : _BaseInstrument
An *Instrument* with the longest maturity.
The *Instrument* with the longest maturity.

Notes
-----
A *Fly* is just a container for three
:class:`~rateslib.instruments.components.protocols._BaseInstrument`, with an overload
for the :meth:`~rateslib.instruments.components.Spread.rate` method to calculate twice the
belly rate minus the wings (whatever metric is in use for each *Instrument*), which allows
it to offer a lot of flexibility in *pseudo Instrument* creation.

"""

_instruments: Sequence[_BaseInstrument]
Expand All @@ -94,7 +132,7 @@ def __init__(
def npv(
self,
*,
curves: Curves_ = NoInput(0),
curves: CurvesT_ = NoInput(0),
solver: Solver_ = NoInput(0),
fx: FXForwards_ = NoInput(0),
fx_vol: FXVolOption_ = NoInput(0),
Expand Down Expand Up @@ -125,7 +163,7 @@ def npv(
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),
Expand Down Expand Up @@ -182,7 +220,7 @@ def local_analytic_rate_fixings(
def cashflows(
self,
*,
curves: Curves_ = NoInput(0),
curves: CurvesT_ = NoInput(0),
solver: Solver_ = NoInput(0),
fx: FXForwards_ = NoInput(0),
fx_vol: FXVolOption_ = NoInput(0),
Expand All @@ -200,73 +238,10 @@ def cashflows(
base=base,
)

def delta(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
Calculate the delta of the *Instrument*.

For arguments see :meth:`Sensitivities.delta()<rateslib.instruments.Sensitivities.delta>`.
"""
return super().delta(*args, **kwargs)

def gamma(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
Calculate the gamma of the *Instrument*.

For arguments see :meth:`Sensitivities.gamma()<rateslib.instruments.Sensitivities.gamma>`.
"""
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),
solver: Solver_ = NoInput(0),
fx: FX_ = NoInput(0),
base: str_ = NoInput(0),
approximate: bool = False,
right: datetime_ = NoInput(0),
) -> DataFrame:
"""
Return a DataFrame of fixing exposures on the *Instruments*.

For arguments see :meth:`XCS.fixings_table()<rateslib.instruments.XCS.fixings_table>`,
and/or :meth:`IRS.fixings_table()<rateslib.instruments.IRS.fixings_table>`

Returns
-------
DataFrame
"""
df_result = DataFrame(
index=DatetimeIndex([], name="obs_dates"),
)
for inst in self.instruments:
try:
df = inst.fixings_table( # type: ignore[attr-defined]
curves=curves,
solver=solver,
fx=fx,
base=base,
approximate=approximate,
right=right,
)
except AttributeError:
continue
df_result = _composit_fixings_table(df_result, df)
return df_result

def rate(
self,
*,
curves: Curves_ = NoInput(0),
curves: CurvesT_ = NoInput(0),
solver: Solver_ = NoInput(0),
fx: FXForwards_ = NoInput(0),
fx_vol: FXVolOption_ = NoInput(0),
Expand Down
3 changes: 2 additions & 1 deletion python/rateslib/instruments/components/fra.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
_Curves,
_maybe_get_curve_maybe_from_solver,
_maybe_get_curve_or_dict_maybe_from_solver,
_maybe_get_curve_or_dict_object_maybe_from_solver,
)
from rateslib.legs.components import FixedLeg, FloatLeg
from rateslib.scheduling import Adjuster
Expand Down Expand Up @@ -555,7 +556,7 @@ def cashflows(

_curves: _Curves = self._parse_curves(curves)
_curves_meta: _Curves = self.kwargs.meta["curves"]
leg2_rate_curve = _maybe_get_curve_or_dict_maybe_from_solver(
leg2_rate_curve = _maybe_get_curve_or_dict_object_maybe_from_solver(
solver=solver, curves_meta=_curves_meta, curves=_curves, name="leg2_rate_curve"
)
scalar = self._try_fra_rate_scalar(leg2_rate_curve=leg2_rate_curve)
Expand Down
101 changes: 32 additions & 69 deletions python/rateslib/instruments/components/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
if TYPE_CHECKING:
from rateslib.typing import (
Any,
Curves_,
CurvesT_,
DualTypes,
FXForwards_,
FXVolOption_,
Expand All @@ -34,61 +34,50 @@ def _instrument_npv(
return instrument.npv(*args, **kwargs)


def _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:
class Portfolio(_BaseInstrument):
"""
Add a DataFrame to an existing fixings table by extending or adding to relevant columns.
A collection of :class:`~rateslib.instruments.components.protocols._BaseInstrument`.

Parameters
----------
df_result: The main DataFrame that will be updated
df: The incoming DataFrame with new data to merge
.. rubric:: Examples

Returns
-------
DataFrame
"""
# reindex the result DataFrame
if df_result.empty:
return df
else:
df_result = df_result.reindex(index=df_result.index.union(df.index))
The following initialises a *Portfolio* of *IRSs*.

# # update existing columns with missing data from the new available data
# for c in [c for c in df.columns if c in df_result.columns and c[1] in ["dcf", "rates"]]:
# df_result[c] = df_result[c].combine_first(df[c])
.. ipython:: python
:suppress:

# merge by addition existing values with missing filled to zero
m = [c for c in df.columns if c in df_result.columns]
if len(m) > 0:
df_result[m] = df_result[m].add(df[m], fill_value=0.0)
from rateslib.instruments.components import Portfolio, IRS
from datetime import datetime as dt

# append new columns without additional calculation
a = [c for c in df.columns if c not in df_result.columns]
if len(a) > 0:
df_result[a] = df[a]
.. ipython:: python

# df_result.columns = MultiIndex.from_tuples(df_result.columns)
return df_result
pf = Portfolio(instruments=[
IRS(dt(2000, 1, 1), "1y", notional=10e3, spec="eur_irs", curves=["estr"]),
IRS(dt(2000, 1, 1), "2y", notional=10e3, spec="eur_irs", curves=["estr"]),
IRS(dt(2000, 1, 1), "3y", notional=10e3, spec="eur_irs", curves=["estr"]),
])
pf.cashflows()

.. rubric:: Pricing

class Portfolio(_BaseInstrument):
"""
Create a collection of *Instruments* to group metrics
Each :class:`~rateslib.instruments.components.protocols._BaseInstrument` should have
its own ``curves`` and ``vol`` objects set at its initialisation, according to the
documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,
these can be universally passed to each *Instrument* but in many cases that would be
technically impossible since each *Instrument* might require difference pricing objects, e.g.
if the *Instruments* have difference currencies. For a *Portfolio*
of three *IRS* in the same currency this would be possible, however.

Parameters
----------
instruments : list
This should be a list of *Instruments*.
instruments : list of _BaseInstrument
The collection of *Instruments*.

Notes
-----
When using a :class:`Portfolio` each *Instrument* must either have pricing parameters
pre-defined using the appropriate :ref:`pricing mechanisms<mechanisms-doc>` or share
common pricing parameters defined at price time.
A *Portfolio* is just a container for multiple
:class:`~rateslib.instruments.components.protocols._BaseInstrument`.
There is no concept of a :meth:`~rateslib.instruments.components.Portfolio.rate`.

Examples
--------
See examples for :class:`Spread` for similar functionality.
"""

_instruments: Sequence[_BaseInstrument]
Expand All @@ -106,7 +95,7 @@ def __init__(self, instruments: Sequence[_BaseInstrument]) -> None:
def npv(
self,
*,
curves: Curves_ = NoInput(0),
curves: CurvesT_ = NoInput(0),
solver: Solver_ = NoInput(0),
fx: FXForwards_ = NoInput(0),
fx_vol: FXVolOption_ = NoInput(0),
Expand Down Expand Up @@ -177,7 +166,7 @@ def npv(
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),
Expand All @@ -196,7 +185,7 @@ def local_analytic_rate_fixings(
def cashflows(
self,
*,
curves: Curves_ = NoInput(0),
curves: CurvesT_ = NoInput(0),
solver: Solver_ = NoInput(0),
fx: FXForwards_ = NoInput(0),
fx_vol: FXVolOption_ = NoInput(0),
Expand All @@ -214,32 +203,6 @@ def cashflows(
base=base,
)

def delta(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
Calculate the delta of the *Instrument*.

For arguments see :meth:`Sensitivities.delta()<rateslib.instruments.Sensitivities.delta>`.
"""
return super().delta(*args, **kwargs)

def gamma(self, *args: Any, **kwargs: Any) -> DataFrame:
"""
Calculate the gamma of the *Instrument*.

For arguments see :meth:`Sensitivities.gamma()<rateslib.instruments.Sensitivities.gamma>`.
"""
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 rate(self, *args: Any, **kwargs: Any) -> NoReturn:
raise NotImplementedError("`rate` is not defined for Portfolio.")

Expand Down
Loading
Loading