Pricing and Trading IRDs - The Difficult Examples - Multi-CSAs (13.3)...

Pricing and Trading IRDs - The Difficult Examples - Multi-CSAs (13.3)...

Pricing and Trading Interest Rate Derivatives

If you happen to have a copy of 'Pricing and Trading Interest Rate Derivatives' you will know that there are some examples contained within that are more difficult than others.

An example, that I think is one of the most difficult to understand in the book, is 'Example 13.3'. This is within the chapter regarding multi-currency risk and it asks readers to consider the intrinsic delta risks of an in-the-money (ITM) EUR IRS which has a multi-currency credit support annex (CSA) in a pricing environment where the cheapest to deliver (CTD) collateral currency is expected to change from EUR to USD midway through the life of the trade.

However, the chapter isn't that unkind - it doesn't jump straight into this. Firstly, it considers an ITM IRS discounted with a local EUR CSA, then it considers the difference if the CSA is USD only, and finally it looks at the multi-currency CSA, leading the reader into what to expect.

In this article we will use rateslib to numerically validate the examples to show readers how useful it is to have access to a pricing library, to explore the example and then go beyond them.

To start, let's look at the prior 'Example 13.2':

No alt text provided for this image

Before we create a risk model let's take a moment to estimate these risks as a sanity check. The book's Section 9.2 gives a table for 'Estimating risk' of IRS of 100mm notional at-the-money (ATM). Since our mid-market rate is going to be 4.5% the ATM delta component should be around 79-80k EUR (or 95-96k USD).

No alt text provided for this image
Estimated local currency delta risk (pv01) of a 100mm notional swap ATM with a flat yield curve.

Section 22.2 also discusses 'Discounting risk'. The formula it gives for estimating it is to take the NPV, multiply it by the tenor of the IRS (10years) and divide by 2 x 10,000 bps. This gives 2.5k EUR (or 3k USD).

Now that we have verified the expectation with a check, we can begin building a a risk model that includes the 10Y EUR-ESTR IRS and which also separates the forecasting RFR curve from the cashflow discounting curve. Technically, for EUR-ESTR these are practically the same curve, but we want to view the explicit risks so we need separate curves. In rateslib we define the two Curves and then calibrate them with a Solver:

>>> from rateslib import *
# this is the ESTR forecasting curve
>>> eurrfr = Curve({dt(2023, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="eurrfr")
# this is the discount curve for EUR cashflows in EUR collateral
>>> eureur = Curve({dt(2023, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="eureur")
# the instruments for the solver are 10Y swap and a basis swap between the curves
>>> instruments = [
      IRS(dt(2023, 1, 1), "10Y", "A", currency="eur", curves=["eurrfr", "eureur"]),
      SBS(dt(2023, 1, 1), "10Y", "A", currency="eur", curves=["eureur", "eureur", "eurrfr", "eureur"]),
    ]
# the 10y rate is set to 4.5% and there is 0bps basis between the curves
>>> solver_eur = Solver(
      curves=[eurrfr, eureur],
      instruments=instruments,
      s=[4.5, 0.0],
      instrument_labels=["10Y dP/dr", "10Y dP/ds"],
      id="EUR",
    )
SUCCESS: `func_tol` reached 4 iters, `f_val`: 2.6e-15, `time`: 0.0211s
        

The example analyses a 5mm EUR ITM IRS and expresses the risk data in USD so we are going need to create the IRS and use an FX conversion:

>>> irs = IRS(
      dt(2023, 7, 1), "10Y", "A", notional=-100e6, currency="eur",
      fixed_rate=5.125, curves=["eurrfr", "eureur"]
    )
>>> fxr = FXRates({"eurusd": 1.20}, settlement=dt(2023, 7, 1)})
>>> irs.npv(solver=solver_eur, base="eur")
<Dual: 5,000,725.52 , ..., ...>

>>> irs.delta(solver=solver_eur, base="usd", fx=fxr)        
No alt text provided for this image

I've highlighted the values that should correspond with the book's estimated -99,000 and +3,000 USD. That's part one of this example demonstrated!

Let's look at the second part of this example:

No alt text provided for this image

Discounting cashflows collateralised in another currency introduces dependency to that currency's rates market and the cross-currency swap market. We need to create a risk model that includes these. To do this we can start by creating the USD model similarly to the EUR model above:

# This is the SOFR forecast curve
>>> usdrfr = Curve({dt(2023, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="usdrfr")
# This is discount curve for USD cashflows collateralised in USD
>>> usdusd = Curve({dt(2023, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="usdusd")
# The instruments are again a swap and a basis swap between curves
>>> instruments = [
      IRS(dt(2023, 1, 1), "10Y", "A", currency="usd", curves=["usdrfr", "usdusd"]),
      SBS(dt(2023, 1, 1), "10Y", "A", currency="usd", curves=["usdusd", "usdusd", "usdrfr", "usdusd"]),
    ]
# To avoid unnecessary complexity the market rates are also set to the same
>>> solver_usd = Solver(
      curves=[usdrfr, usdusd],
      instruments=instruments,
      s=[4.5, 0.0],
      instrument_labels=["10Y dP/dr", "10Y dP/ds"],
      id="USD",
    )
SUCCESS: `func_tol` reached 5 iters, `f_val`: 1.9e-29, `time`: 0.1024s        

Then we add some cross-currency dependency, linking the above two models:

# This curve discounts EUR cashflows collateralised in USD.
# This curve is priced from cross-currency basis.
>>> eurusd = Curve({dt(2023, 7, 1): 1.0, dt(2033, 7, 1): 1.0, id="eurusd")
>>> fxf = FXForwards(
      fx_rates=fxr,
      fx_curves={
         "eureur": eureur,
         "usdusd": usdusd,
         "eurusd": eurusd,
      }
    )
>>> instruments = [
      XCS(
        dt(2023, 7, 1), "10Y", "A", currency="eur", leg2_currency="usd", 
        curves=["eurrfr", "eurusd", "usdrfr", "usdusd"]
      )
    ]
>>> solver = Solver(
      pre_solvers=[solver_eur, solver_usd],
      curves=[eurusd],
      s=[0.0],
      instruments=instruments,
      instrument_labels=["10Y"],
      fx=fxf,
      id="XCS",
    )
SUCCESS: `func_tol` reached 5 iters, `f_val`: 1.9e-25, `time`: 0.0995s        

With the Curves and Solver setup we can construct our IRS again, this time collateralised with USD (as specified by its discount curve) and get the risks:

>>> irs = IRS(
      dt(2023, 7, 1), "10Y", "A", notional=-100e6, currency="eur",
      fixed_rate=5.125, curves=["eurrfr", "eurusd"]
    )
>>> irs.delta(solver=solver, base="usd")        
No alt text provided for this image

Again, I've highlighted the values that compare very closely with those stated in the book.

Finally, we can tackle the intended 'Example 13.3':

No alt text provided for this image

From the previous two examples we can broadly predict what to expect. The first 5Y should have discount risk expressed in the EUR CSA framework, and in the 5Y5Y section the discount risk should be expressed in the USD CSA framework. To complete this example we need to rebuild all our risk models noting this time that we also need a 5Y node and degree of freedom within the Curves.

So we redefine all the Curves below and note how we are also going add in an intrinsic multi-CSA curve.

(note as of July '23 this uses a pre-release version of rateslib containing multi-csa features not available in the most recent v0.2.0)

eurrfr = Curve({dt(2023, 7, 1): 1.0, dt(2028, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="eurrfr"
eureur = Curve({dt(2023, 7, 1): 1.0, dt(2028, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="eureur")
eurusd = Curve({dt(2023, 7, 1): 1.0, dt(2028, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="eurusd")
usdrfr = Curve({dt(2023, 7, 1): 1.0, dt(2028, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="usdrfr")
usdusd = Curve({dt(2023, 7, 1): 1.0, dt(2028, 7, 1): 1.0, dt(2033, 7, 1): 1.0}, id="usdusd")
eur_eurusd = CompositeCurve([eureur, eurusd], multi_csa=True, id="eur_eurusd"))        

Then we redefine our IRS, SBS and XCS instruments:

eur_insts = [
? ? IRS(dt(2023, 7, 1), "5y", "A", currency="eur", curves=["eurrfr", "eureur"]),
? ? IRS(dt(2023, 7, 1), "10y", "A", currency="eur", curves=["eurrfr", "eureur"]),
? ? SBS(dt(2023, 7, 1), "5y", "A", currency="eur", curves=["eureur", "eureur", "eurrfr", "eureur"]),
? ? SBS(dt(2023, 7, 1), "10y", "A", currency="eur", curves=["eureur", "eureur", "eurrfr", "eureur"]),
]
usd_insts = [
? ? IRS(dt(2023, 7, 1), "5y", "A", currency="usd", curves=["usdrfr", "usdusd"]),
? ? IRS(dt(2023, 7, 1), "10y", "A", currency="usd", curves=["usdrfr", "usdusd"]),
? ? SBS(dt(2023, 7, 1), "5y", "A", currency="usd", curves=["usdusd", "usdusd", "usdrfr", "usdusd"]),
? ? SBS(dt(2023, 7, 1), "10y", "A", currency="usd", curves=["usdusd", "usdusd", "usdrfr", "usdusd"]),
]
xcs_insts = [
? ? XCS(dt(2023, 7, 1), "5Y", "A", currency="eur", leg2_currency="usd", curves=["eurrfr", "eurusd", "usdrfr", "usdusd"]),
? ? XCS(dt(2023, 7, 1), "10Y", "A", currency="eur", leg2_currency="usd", curves=["eurrfr", "eurusd", "usdrfr", "usdusd"])
]        

And we add a new FXForwards and recreate the Solver (this time solving everything simultaneously, which is just easier in this case):

fxf = FXForwards(
? ? fx_rates=FXRates({"eurusd": 1.20}, settlement=dt(2023, 7, 1)),
? ? fx_curves={
? ? ? ? "eureur": eureur,
? ? ? ? "eurusd": eurusd,
? ? ? ? "usdusd": usdusd,
? ? }
)
solver = Solver(
? ? curves=[eurrfr, eureur, eurusd, usdusd, usdrfr, eur_eurusd],
? ? instruments=eur_insts+usd_insts+xcs_insts,
? ? s=[4.5, 4.5, 0., 0., 4.5, 4.5, 0., 0., -7.5, -0.75],
? ? instrument_labels=[
? ? ? ? "EUR 5Y dP/dr", "EUR 10Y dP/dr", "EUR 5Y dP/ds", "EUR 10Y dP/ds",
? ? ? ? "USD 5Y dP/dr", "USD 10Y dP/dr", "USD 5Y dP/ds", "USD 10Y dP/ds",
? ? ? ? "XCS 5Y", "XCS 10Y",
? ? ],
? ? fx=fxf,
? ? id="ALL",
)        

So what do we get?

>>> irs = IRS(
      dt(2023, 7, 1), "10y", "A", notional=-100e6, fixed_rate=5.125,
      curves=["eurrfr", "eur_eurusd"], currency="eur"
    )
>>> irs.delta(solver=solver, base="usd")        
No alt text provided for this image

This time, we are a little further off than the book's values, but only marginally. There are few reasons for this.

The first is that the example doesn't explicitly state what the market rates are, and these have an impact on the results becuase this IRS experiences cross-gamma (try doing irs.gamma(solver=solver, base="usd")). In this example I made up the rates and I chose to keep the curve flat at 4.5%.

The second reason is that, for simplicity, the example chose to apportion the discount risk versus the EUR CSA in the 5Y and USD CSA in the 5Y5Y section 50% each. This isn't real in practice, with a flat curve its more like 75%, 25%, and latter sections of the book such as Figure 21.2 and Figure 21.3 explain why. The examples in the book are a mix of qualitative and quantitative to indicate concepts and it wasn't needed to overcomplicate this example.

The third reason regarding the 5Y and 10Y EUR delta is that it was either my own oversight, or I felt it was more pedagogicial to highlight the lower overall EUR delta as a single number.

In the EUR CSA case the total dP/dr EUR delta is: -99,007,

In the EUR+USD CSA case the total dP/dr EUR delta is: -98,912, as predicted.

The book also claims that the NPV is slightly lower than 5mm EUR. Is this true? Indeed so, the NPV falls by 4,794 EUR.

>>> irs.npv(solver=solver, base="eur")
<Dual: 4,995,931.24 , ..., ... >        

Anything to add?

In the last Solver the XCS rates I used were chosen to make EUR CTD collateral in the first 5Y and USD the CTD in the latter 5Y. We plot the overnight rate curves to check that this was actually the case.

>>> eureur.plot(
       "1d", comparators=[eurusd, eur_eurusd], 
       labels=["eur:eur", "eur:usd", "eur:eur+usd"]
    )        
No alt text provided for this image
Overnight rates for the EUR discounting curves.

We can observe that in the latter 5Y the multi currency discount curve (green) is about 7.5bps higher than the local EUR CSA discount curve (blue). Also, observing the USD discount delta for this section of the curve totals about 633 EUR/bp this implies the difference between the IRS NPVs should be roughly about 633 * 7.5bps = 4,748 EUR. We calculated it exactly at 4,794 EUR.

Everything would appear to be consistent!

Arpita Chakraborty

Risk and Valuation |Model Risk & Validation| Structured Finance| asset backed securities | Regulatory Reporting | IRRBB| Fixed Income Securities| RMBS|CDO | Credit Risk | Analytics | Python|Capital Control

1 年

Immensely grateful to have the book and meet the author personally!!very well written.Thank you!

要查看或添加评论,请登录

社区洞察

其他会员也浏览了