Skip to content

Commit 75b1da9

Browse files
authored
TYPDOC: extend for new instrument components (#172) (#1095)
1 parent 6a80f19 commit 75b1da9

File tree

11 files changed

+612
-345
lines changed

11 files changed

+612
-345
lines changed

pyproject.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,19 @@ exclude = [
153153
"/instruments/components/fx_options",
154154
# "/instruments/components/protocols",
155155
# "/instruments/components/cds.py",
156-
"/instruments/components/fly.py",
156+
# "/instruments/components/fly.py",
157157
# "/instruments/components/fra.py",
158158
# "/instruments/components/fx_forward.py",
159159
# "/instruments/components/fx_swap.py",
160-
"/instruments/components/fx_vol_value.py",
160+
# "/instruments/components/fx_vol_value.py",
161161
# "/instruments/components/iirs.py",
162162
# "/instruments/components/irs.py",
163163
# "/instruments/components/ndf.py",
164-
"/instruments/components/portfolio.py",
164+
# "/instruments/components/portfolio.py",
165165
# "/instruments/components/sbs.py",
166-
"/instruments/components/spread.py",
167-
"/instruments/components/stir_future.py",
168-
"/instruments/components/value.py",
166+
# "/instruments/components/spread.py",
167+
# "/instruments/components/stir_future.py",
168+
# "/instruments/components/value.py",
169169
# "/instruments/components/xcs.py",
170170
# "/instruments/components/zcis.py",
171171
# "/instruments/components/zcs.py",

python/rateslib/instruments/components/fly.py

Lines changed: 47 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
if TYPE_CHECKING:
1616
from rateslib.typing import (
1717
Any,
18-
Curves_,
18+
CurvesT_,
1919
DualTypes,
2020
FXForwards_,
2121
FXVolOption_,
@@ -64,16 +64,54 @@ def _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:
6464

6565
class Fly(_BaseInstrument):
6666
"""
67-
Create a butterfly of *Instruments*.
67+
A *Butterfly* of :class:`~rateslib.instruments.components.protocols._BaseInstrument`.
68+
69+
.. rubric:: Examples
70+
71+
The following initialises a *Butterfly* of *IRSs*.
72+
73+
.. ipython:: python
74+
:suppress:
75+
76+
from rateslib.instruments.components import Fly, IRS
77+
from datetime import datetime as dt
78+
79+
.. ipython:: python
80+
81+
fly = Fly(
82+
instrument1=IRS(dt(2000, 1, 1), "1y", notional=10e6, spec="eur_irs", curves=["estr"]),
83+
instrument2=IRS(dt(2000, 1, 1), "2y", notional=-5e6, spec="eur_irs", curves=["estr"]),
84+
instrument3=IRS(dt(2000, 1, 1), "3y", notional=1.75e6, spec="eur_irs", curves=["estr"]),
85+
)
86+
fly.cashflows()
87+
88+
.. rubric:: Pricing
89+
90+
Each :class:`~rateslib.instruments.components.protocols._BaseInstrument` should have
91+
its own ``curves`` and ``vol`` objects set at its initialisation, according to the
92+
documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,
93+
these can be universally passed to each *Instrument* but in many cases that would be
94+
technically impossible since each *Instrument* might require difference pricing objects, e.g.
95+
if the *Instruments* have difference currencies. For a *Fly*
96+
of three *IRS* in the same currency this would be possible, however.
6897
6998
Parameters
7099
----------
71100
instrument1 : _BaseInstrument
72-
An *Instrument* with the shortest maturity.
101+
The *Instrument* with the shortest maturity.
73102
instrument2 : _BaseInstrument
74-
The *Instrument* of the body of the *Fly*.
103+
The *Instrument* with the intermediate maturity.
75104
instrument3 : _BaseInstrument
76-
An *Instrument* with the longest maturity.
105+
The *Instrument* with the longest maturity.
106+
107+
Notes
108+
-----
109+
A *Fly* is just a container for three
110+
:class:`~rateslib.instruments.components.protocols._BaseInstrument`, with an overload
111+
for the :meth:`~rateslib.instruments.components.Spread.rate` method to calculate twice the
112+
belly rate minus the wings (whatever metric is in use for each *Instrument*), which allows
113+
it to offer a lot of flexibility in *pseudo Instrument* creation.
114+
77115
"""
78116

79117
_instruments: Sequence[_BaseInstrument]
@@ -94,7 +132,7 @@ def __init__(
94132
def npv(
95133
self,
96134
*,
97-
curves: Curves_ = NoInput(0),
135+
curves: CurvesT_ = NoInput(0),
98136
solver: Solver_ = NoInput(0),
99137
fx: FXForwards_ = NoInput(0),
100138
fx_vol: FXVolOption_ = NoInput(0),
@@ -125,7 +163,7 @@ def npv(
125163
def local_analytic_rate_fixings(
126164
self,
127165
*,
128-
curves: Curves_ = NoInput(0),
166+
curves: CurvesT_ = NoInput(0),
129167
solver: Solver_ = NoInput(0),
130168
fx: FXForwards_ = NoInput(0),
131169
fx_vol: FXVolOption_ = NoInput(0),
@@ -182,7 +220,7 @@ def local_analytic_rate_fixings(
182220
def cashflows(
183221
self,
184222
*,
185-
curves: Curves_ = NoInput(0),
223+
curves: CurvesT_ = NoInput(0),
186224
solver: Solver_ = NoInput(0),
187225
fx: FXForwards_ = NoInput(0),
188226
fx_vol: FXVolOption_ = NoInput(0),
@@ -200,73 +238,10 @@ def cashflows(
200238
base=base,
201239
)
202240

203-
def delta(self, *args: Any, **kwargs: Any) -> DataFrame:
204-
"""
205-
Calculate the delta of the *Instrument*.
206-
207-
For arguments see :meth:`Sensitivities.delta()<rateslib.instruments.Sensitivities.delta>`.
208-
"""
209-
return super().delta(*args, **kwargs)
210-
211-
def gamma(self, *args: Any, **kwargs: Any) -> DataFrame:
212-
"""
213-
Calculate the gamma of the *Instrument*.
214-
215-
For arguments see :meth:`Sensitivities.gamma()<rateslib.instruments.Sensitivities.gamma>`.
216-
"""
217-
return super().gamma(*args, **kwargs)
218-
219-
def exo_delta(self, *args: Any, **kwargs: Any) -> DataFrame:
220-
"""
221-
Calculate the delta of the *Instrument* measured
222-
against user defined :class:`~rateslib.dual.Variable`.
223-
224-
For arguments see
225-
:meth:`Sensitivities.exo_delta()<rateslib.instruments.Sensitivities.exo_delta>`.
226-
"""
227-
return super().exo_delta(*args, **kwargs)
228-
229-
def fixings_table(
230-
self,
231-
curves: Curves_ = NoInput(0),
232-
solver: Solver_ = NoInput(0),
233-
fx: FX_ = NoInput(0),
234-
base: str_ = NoInput(0),
235-
approximate: bool = False,
236-
right: datetime_ = NoInput(0),
237-
) -> DataFrame:
238-
"""
239-
Return a DataFrame of fixing exposures on the *Instruments*.
240-
241-
For arguments see :meth:`XCS.fixings_table()<rateslib.instruments.XCS.fixings_table>`,
242-
and/or :meth:`IRS.fixings_table()<rateslib.instruments.IRS.fixings_table>`
243-
244-
Returns
245-
-------
246-
DataFrame
247-
"""
248-
df_result = DataFrame(
249-
index=DatetimeIndex([], name="obs_dates"),
250-
)
251-
for inst in self.instruments:
252-
try:
253-
df = inst.fixings_table( # type: ignore[attr-defined]
254-
curves=curves,
255-
solver=solver,
256-
fx=fx,
257-
base=base,
258-
approximate=approximate,
259-
right=right,
260-
)
261-
except AttributeError:
262-
continue
263-
df_result = _composit_fixings_table(df_result, df)
264-
return df_result
265-
266241
def rate(
267242
self,
268243
*,
269-
curves: Curves_ = NoInput(0),
244+
curves: CurvesT_ = NoInput(0),
270245
solver: Solver_ = NoInput(0),
271246
fx: FXForwards_ = NoInput(0),
272247
fx_vol: FXVolOption_ = NoInput(0),

python/rateslib/instruments/components/fra.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
_Curves,
1313
_maybe_get_curve_maybe_from_solver,
1414
_maybe_get_curve_or_dict_maybe_from_solver,
15+
_maybe_get_curve_or_dict_object_maybe_from_solver,
1516
)
1617
from rateslib.legs.components import FixedLeg, FloatLeg
1718
from rateslib.scheduling import Adjuster
@@ -555,7 +556,7 @@ def cashflows(
555556

556557
_curves: _Curves = self._parse_curves(curves)
557558
_curves_meta: _Curves = self.kwargs.meta["curves"]
558-
leg2_rate_curve = _maybe_get_curve_or_dict_maybe_from_solver(
559+
leg2_rate_curve = _maybe_get_curve_or_dict_object_maybe_from_solver(
559560
solver=solver, curves_meta=_curves_meta, curves=_curves, name="leg2_rate_curve"
560561
)
561562
scalar = self._try_fra_rate_scalar(leg2_rate_curve=leg2_rate_curve)

python/rateslib/instruments/components/portfolio.py

Lines changed: 32 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
if TYPE_CHECKING:
1717
from rateslib.typing import (
1818
Any,
19-
Curves_,
19+
CurvesT_,
2020
DualTypes,
2121
FXForwards_,
2222
FXVolOption_,
@@ -34,61 +34,50 @@ def _instrument_npv(
3434
return instrument.npv(*args, **kwargs)
3535

3636

37-
def _composit_fixings_table(df_result: DataFrame, df: DataFrame) -> DataFrame:
37+
class Portfolio(_BaseInstrument):
3838
"""
39-
Add a DataFrame to an existing fixings table by extending or adding to relevant columns.
39+
A collection of :class:`~rateslib.instruments.components.protocols._BaseInstrument`.
4040
41-
Parameters
42-
----------
43-
df_result: The main DataFrame that will be updated
44-
df: The incoming DataFrame with new data to merge
41+
.. rubric:: Examples
4542
46-
Returns
47-
-------
48-
DataFrame
49-
"""
50-
# reindex the result DataFrame
51-
if df_result.empty:
52-
return df
53-
else:
54-
df_result = df_result.reindex(index=df_result.index.union(df.index))
43+
The following initialises a *Portfolio* of *IRSs*.
5544
56-
# # update existing columns with missing data from the new available data
57-
# for c in [c for c in df.columns if c in df_result.columns and c[1] in ["dcf", "rates"]]:
58-
# df_result[c] = df_result[c].combine_first(df[c])
45+
.. ipython:: python
46+
:suppress:
5947
60-
# merge by addition existing values with missing filled to zero
61-
m = [c for c in df.columns if c in df_result.columns]
62-
if len(m) > 0:
63-
df_result[m] = df_result[m].add(df[m], fill_value=0.0)
48+
from rateslib.instruments.components import Portfolio, IRS
49+
from datetime import datetime as dt
6450
65-
# append new columns without additional calculation
66-
a = [c for c in df.columns if c not in df_result.columns]
67-
if len(a) > 0:
68-
df_result[a] = df[a]
51+
.. ipython:: python
6952
70-
# df_result.columns = MultiIndex.from_tuples(df_result.columns)
71-
return df_result
53+
pf = Portfolio(instruments=[
54+
IRS(dt(2000, 1, 1), "1y", notional=10e3, spec="eur_irs", curves=["estr"]),
55+
IRS(dt(2000, 1, 1), "2y", notional=10e3, spec="eur_irs", curves=["estr"]),
56+
IRS(dt(2000, 1, 1), "3y", notional=10e3, spec="eur_irs", curves=["estr"]),
57+
])
58+
pf.cashflows()
7259
60+
.. rubric:: Pricing
7361
74-
class Portfolio(_BaseInstrument):
75-
"""
76-
Create a collection of *Instruments* to group metrics
62+
Each :class:`~rateslib.instruments.components.protocols._BaseInstrument` should have
63+
its own ``curves`` and ``vol`` objects set at its initialisation, according to the
64+
documentation for that *Instrument*. For the pricing methods ``curves`` and ``vol`` objects,
65+
these can be universally passed to each *Instrument* but in many cases that would be
66+
technically impossible since each *Instrument* might require difference pricing objects, e.g.
67+
if the *Instruments* have difference currencies. For a *Portfolio*
68+
of three *IRS* in the same currency this would be possible, however.
7769
7870
Parameters
7971
----------
80-
instruments : list
81-
This should be a list of *Instruments*.
72+
instruments : list of _BaseInstrument
73+
The collection of *Instruments*.
8274
8375
Notes
8476
-----
85-
When using a :class:`Portfolio` each *Instrument* must either have pricing parameters
86-
pre-defined using the appropriate :ref:`pricing mechanisms<mechanisms-doc>` or share
87-
common pricing parameters defined at price time.
77+
A *Portfolio* is just a container for multiple
78+
:class:`~rateslib.instruments.components.protocols._BaseInstrument`.
79+
There is no concept of a :meth:`~rateslib.instruments.components.Portfolio.rate`.
8880
89-
Examples
90-
--------
91-
See examples for :class:`Spread` for similar functionality.
9281
"""
9382

9483
_instruments: Sequence[_BaseInstrument]
@@ -106,7 +95,7 @@ def __init__(self, instruments: Sequence[_BaseInstrument]) -> None:
10695
def npv(
10796
self,
10897
*,
109-
curves: Curves_ = NoInput(0),
98+
curves: CurvesT_ = NoInput(0),
11099
solver: Solver_ = NoInput(0),
111100
fx: FXForwards_ = NoInput(0),
112101
fx_vol: FXVolOption_ = NoInput(0),
@@ -177,7 +166,7 @@ def npv(
177166
def local_analytic_rate_fixings(
178167
self,
179168
*,
180-
curves: Curves_ = NoInput(0),
169+
curves: CurvesT_ = NoInput(0),
181170
solver: Solver_ = NoInput(0),
182171
fx: FXForwards_ = NoInput(0),
183172
fx_vol: FXVolOption_ = NoInput(0),
@@ -196,7 +185,7 @@ def local_analytic_rate_fixings(
196185
def cashflows(
197186
self,
198187
*,
199-
curves: Curves_ = NoInput(0),
188+
curves: CurvesT_ = NoInput(0),
200189
solver: Solver_ = NoInput(0),
201190
fx: FXForwards_ = NoInput(0),
202191
fx_vol: FXVolOption_ = NoInput(0),
@@ -214,32 +203,6 @@ def cashflows(
214203
base=base,
215204
)
216205

217-
def delta(self, *args: Any, **kwargs: Any) -> DataFrame:
218-
"""
219-
Calculate the delta of the *Instrument*.
220-
221-
For arguments see :meth:`Sensitivities.delta()<rateslib.instruments.Sensitivities.delta>`.
222-
"""
223-
return super().delta(*args, **kwargs)
224-
225-
def gamma(self, *args: Any, **kwargs: Any) -> DataFrame:
226-
"""
227-
Calculate the gamma of the *Instrument*.
228-
229-
For arguments see :meth:`Sensitivities.gamma()<rateslib.instruments.Sensitivities.gamma>`.
230-
"""
231-
return super().gamma(*args, **kwargs)
232-
233-
def exo_delta(self, *args: Any, **kwargs: Any) -> DataFrame:
234-
"""
235-
Calculate the delta of the *Instrument* measured
236-
against user defined :class:`~rateslib.dual.Variable`.
237-
238-
For arguments see
239-
:meth:`Sensitivities.exo_delta()<rateslib.instruments.Sensitivities.exo_delta>`.
240-
"""
241-
return super().exo_delta(*args, **kwargs)
242-
243206
def rate(self, *args: Any, **kwargs: Any) -> NoReturn:
244207
raise NotImplementedError("`rate` is not defined for Portfolio.")
245208

0 commit comments

Comments
 (0)