[1]:
from rateslib import *

Valuing Historical Swaps at Today’s Date#

A common operation is to want to value a swap which has an effective date in the past. To do this, the swap should not be altered in its definition but it likely requires additional pricing information supplied, in the form of fixings. See ‘Cookbook > Working with Fixings’ for more detail on this aspect.

[2]:
curve = Curve({dt(2024, 7, 3): 1.0, dt(2025, 7, 3): 0.95}, calendar="nyc")
irs = IRS(dt(2024, 6, 26), "1m", spec="usd_irs", curves=curve, fixed_rate=5.00)

This swap cant price directly because today is 3rd July and it started on 26th June and it is missing information on some of the fixings.

[3]:
try:
    irs.npv()
except ValueError as e:
    print(e)
`effective` date for rate period is before the initial node date of the Curve.
If you are trying to calculate a rate for an historical FloatPeriod have you neglected to supply appropriate `fixings`?
See Documentation > Cookbook > Working with Fixings.

The SOFR fixings are needed for the following reference value dates in the past (as measured as of 3rd July 2024):

[4]:
get_calendar("nyc").bus_date_range(dt(2024, 6, 26), dt(2024, 7, 2))
[4]:
[datetime.datetime(2024, 6, 26, 0, 0),
 datetime.datetime(2024, 6, 27, 0, 0),
 datetime.datetime(2024, 6, 28, 0, 0),
 datetime.datetime(2024, 7, 1, 0, 0),
 datetime.datetime(2024, 7, 2, 0, 0)]

Normally these fixings would be populated by some automated data collection service or CSV file upload, but here they have been manually inserted.

[5]:
from pandas import Series
fixings.add("sofr_fixings_1B", Series(
    data=[5.34, 5.34, 5.33, 5.40, 5.35],
    index=get_calendar("nyc").bus_date_range(dt(2024, 6, 26), dt(2024, 7, 2))
))
fixings["sofr_fixings_1B"]
[5]:
(-2914781527391766233,
 reference_date
 2024-06-26    5.34
 2024-06-27    5.34
 2024-06-28    5.33
 2024-07-01    5.40
 2024-07-02    5.35
 Name: rate, dtype: float64,
 (Timestamp('2024-06-26 00:00:00'), Timestamp('2024-07-02 00:00:00')))

The definition of the swap is altered to also input the fixings and then it is repriced with the curve, correctly, without raising errors.

[6]:
irs = IRS(dt(2024, 6, 26), "1m", spec="usd_irs", curves=curve, fixed_rate=5.0, leg2_rate_fixings="sofr_fixings")
irs.npv()
[6]:
np.float64(113.51930516687025)

Alternatively the cashflows method gives an alternative, holistic perspective.

[7]:
irs.cashflows()
[7]:
Type Ccy Payment Notional Period Convention DCF Acc Start Acc End DF Cashflow NPV FX Rate Base Ccy NPV Ccy Collateral Rate Spread
leg1 0 FixedPeriod USD 2024-07-30 1000000.0 Stub Act360 0.083333 2024-06-26 2024-07-26 0.996213 -4166.666667 -4150.887045 1.0 USD -4150.887045 None 5.000000 NaN
leg2 0 FloatPeriod USD 2024-07-30 -1000000.0 Stub Act360 0.083333 2024-06-26 2024-07-26 0.996213 4280.617516 4264.406350 1.0 USD 4264.406350 None 5.136741 0.0

Valuing Spot Swaps at Future Dates#

The current design of rateslib targets accurate evaluation of instruments at today’s date. Valuing swaps as of future dates is something that might be required in calculations such as those for XVA or scenario analysis.

Currently rateslib does have some limited features to permit this.

As an example suppose today’s date is 3rd July 2024 and we construct an 11m IRS with a monthly frequency. A more granular Curve is constructed in this example.

[8]:
curve = Curve(
    nodes={
        dt(2024, 7, 3): 1.0,
        dt(2024, 8, 3): 1.0,
        dt(2024, 9, 3): 1.0,
        dt(2024, 10, 3): 1.0,
        dt(2024, 11, 3): 1.0,
        dt(2024, 12, 3): 1.0,
        dt(2025, 1, 3): 1.0,
        dt(2025, 2, 3): 1.0,
        dt(2025, 3, 3): 1.0,
        dt(2025, 4, 3): 1.0,
        dt(2025, 5, 3): 1.0,
        dt(2025, 6, 3): 1.0,
        dt(2025, 7, 3): 1.0,
    },
    calendar="nyc",
)
solver = Solver(
    curves=[curve],
    instruments=[
        IRS(dt(2024, 7, 3), _, spec="usd_irs", curves=curve) for _ in
        ["1m", "2m", "3m", "4m", "5m", "6m", "7m", "8m", "9m", "10m", "11m", "1y"]
    ],
    s=[5.33, 5.33, 5.315, 5.28, 5.25, 5.21, 5.17, 5.15, 5.11, 5.07, 5.04, 5.00]
)
SUCCESS: `func_tol` reached after 3 iterations (levenberg_marquardt), `f_val`: 2.4782111537134525e-12, `time`: 0.0050s
[9]:
irs = IRS(dt(2024, 7, 3), "11m", "M", spec="usd_irs", curves=curve, fixed_rate=5.04)
irs.npv()
[9]:
<Dual: -912.778704, (bda5d0, bda5d1, bda5d2, ...), [999706.0, -4333.0, -4631.2, ...]>

Today’s date as the valuation evaluation date#

Today’s date, as the evaluation date, is derived from the initial node date of the discount curve, i.e. the date at which the discount factor is necessarily set to be equal to 1.0.

The initial node date can be translated on a Curve to a date in the future as follows.

[10]:
translated_curve = curve.translate(dt(2024, 10, 15))

If the swap is then attempted to be priced with this translated Curve it will fail for the same reason as the section above - it cannot forecast rates before it starts.

[11]:
try:
    irs.npv(curves=translated_curve)
except ValueError as e:
    print(e)
`effective` date for rate period is before the initial node date of the Curve.
If you are trying to calculate a rate for an historical FloatPeriod have you neglected to supply appropriate `fixings`?
See Documentation > Cookbook > Working with Fixings.

However, in this case the fixings are not known because 15th October 2024 is a future date. The solution is to supply a separate forecast curve and discount curve, where the forecast curve is capable of calculating the relevant rates. In this case the forecast curve is the very same curve that is created as of today’s date.

[12]:
irs.npv(curves=[curve, translated_curve])
[12]:
<Dual: -1572.693551, (bda5d0, bda5d1, bda5d2, ...), [0.0, 0.0, 0.0, ...]>

When the cashflows are analysed, one can visually inspect that any cashflow that is paid before the initial node date of the discount curve is set to zero value. The floating rates are demonstarted here showing that the floating rates have still been correctly forecast for each FloatPeriod.

[13]:
irs.cashflows(curves=[curve, translated_curve])[12:16]
[13]:
Type Ccy Payment Notional Period Convention DCF Acc Start Acc End DF Cashflow NPV FX Rate Base Ccy NPV Ccy Collateral Rate Spread
leg2 1 FloatPeriod USD 2024-09-05 -1000000.0 Regular Act360 0.080556 2024-08-05 2024-09-03 0.000000 4272.735240 0.000000 1.0 USD 0.000000 None 5.304085 0.0
2 FloatPeriod USD 2024-10-07 -1000000.0 Regular Act360 0.083333 2024-09-03 2024-10-03 0.000000 4363.280847 0.000000 1.0 USD 0.000000 None 5.235937 0.0
3 FloatPeriod USD 2024-11-06 -1000000.0 Regular Act360 0.088889 2024-10-03 2024-11-04 0.996894 4542.193325 4528.086810 1.0 USD 4528.086810 None 5.109967 0.0
4 FloatPeriod USD 2024-12-05 -1000000.0 Regular Act360 0.080556 2024-11-04 2024-12-03 0.992878 4052.138539 4023.279633 1.0 USD 4023.279633 None 5.030241 0.0

Plotting Future Value#

[14]:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)

def _npv(date):
    t_curve = curve.translate(date)
    return irs.npv(curves=[curve, t_curve])

dates=[
    dt(2024, 7, 4),
    dt(2024, 8, 10),
    dt(2024, 9, 10),
    dt(2024, 10, 10),
    dt(2024, 11, 10),
    dt(2024, 12, 10),
    dt(2025, 1, 10),
    dt(2025, 2, 10),
    dt(2025, 3, 10),
    dt(2025, 4, 10),
    dt(2025, 5, 10),
    dt(2025, 6, 10),
]

ax.plot(dates, [_npv(_) for _ in dates])
[14]:
[<matplotlib.lines.Line2D at 0x10b3f9940>]
_images/z_historical_swap_26_1.png

The general structure of this plot is as described in the section on ‘Cash, Collateral and Credit’ in Pricing and Trading Interest Rate Derivatives.