It is best to understand this from basic principles.
I will construct these instruments in Python's rateslib
so that you can also visualise the cashflows and the delta.
Calculation Engine
In order to do this we need to set up a framework. This framework will comprise, interest rate curves in EUR and USD and a curve that values EUR cashflows in USD collateral (acting as the basis curve).
from rateslib import *
usdusd = Curve({dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0}, id="usdusd")
eureur = Curve({dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0}, id="eureur")
eurusd = Curve({dt(2023, 1, 1): 1.0, dt(2024, 1, 1): 1.0}, id="eurusd")
fxr = FXRates({"eurusd": 1.10}, settlement=dt(2023, 1, 1))
fxf = FXForwards(
fx_rates=fxr,
fx_curves={
"usdusd": usdusd,
"eureur": eureur,
"eurusd": eurusd,
}
)
Next we calibrate our curves so that the 1Y USD swap is 5% the 1Y EUR swap at 3.5% and the cross-currency basis swap is -10bps.
solver = Solver(
curves=[usdusd, eureur, eurusd],
instruments=[
IRS(dt(2023, 1, 1), "1Y", "A", currency="usd", curves="usdusd"),
IRS(dt(2023, 1, 1), "1Y", "A", currency="eur", curves="eureur"),
XCS(dt(2023, 1, 1), "1Y", "Q", currency="eur", leg2_currency="usd", curves=["eureur", "eurusd", "usdusd", "usdusd"])
],
s=[5.0, 3.5, -10.0],
instrument_labels=["1Y USD", "1Y EUR", "1Y EUR/USD"],
fx=fxf,
)
SUCCESS: `func_tol` reached after 3 iters, `f_val`: 3.96e-17, `time`: 35ms
1) The equivalence of FXSwap
and 2 FXExchange
s (or forwards)
Next we will show that an FXSwap
is essentially 2 FXExchanges
, which are two forward FX transactions.
A market agreed FXSwap contains 4 fixed cashflows. Create a 3M FXSwap and have a look:
fxs = FXSwap(
effective=dt(2023, 1, 1),
terminination="3M",
currency="eur",
leg2_currency="usd",
fx_fixing=1.10,
points=42.335246,
curves=[None, "eurusd", None, "usdusd"]
)
fxs.cashflows_table(solver=solver)
Now we build the replicating trades as 2 FXExchanges
in the opposite directions.
args = dict(
currency="eur",
leg2_currency="usd",
curves=[None, "eurusd", None, "usdusd"]
)
fxe1 = FXExchange(
settlement=dt(2023, 1, 1),
notional=1e6,
fx_rate=1.10,
**args
)
fxe2 = FXExchange(
settlement=dt(2023, 4, 1),
notional=-1e6,
fx_rate=1.1042335246,
**args
)
If we add the FXSwap
and the replicating FXExchanges
to a combined Portfolio
we can see that all the cashflows and delta risks net out.
pf = Portfolio([fxs, fxe1, fxe2])
pf.cashflows_table(solver=solver)
pf.delta(solver=solver)
2) The equivalence of an FXSwap
and a single period NonMtmFixedFixedXCS
Now we can show that the 4 cashflows of an FXSwap
(which is just 2 FX forwards) can replicate a non-mark-to-market fixed-fixed cross-currency swap.
ffxcs = NonMtmFixedFixedXCS(
effective=dt(2023, 1, 1),
termination="3M",
frequency="A",
currency="eur",
notional=-1e6,
leg2_currency="usd",
fixed_rate=0.0,
leg2_fixed_rate=1.539463,
fx_fixing=1.10,
payment_lag=0,
curves=[None, "eurusd", None, "usdusd"],
)
Since these legs are fixed rate all the cashflows are fixed, again, so we can see the cashflows table:
ffxcs.cashflows_table(solver=solver)
This is the opposite of the original FXSwap
. If we combine the instruments to a Portfolio
we see they net out.
pf = Portfolio([fxs, ffxcs])
pf.cashflows_table(solver=solver)
pf.delta(solver=solver)
3) The Equiavlence of a NonMtmFixedFixedXCS
with 2 IRS
and 1 NonMtmXCS
So lastly we show that a non-mtm float-float cross-currency swap plus 2 interest rate swaps are equivalently a non-mtm fixed-fixed cross-currency swap.
xcs = NonMtmXCS(
effective=dt(2023, 1, 1),
termination="3M",
frequency="A",
currency="eur",
notional=-1e6,
leg2_currency="usd",
fx_fixing=1.10,
payment_lag=0,
curves=["eureur", "eurusd", "usdusd", "usdusd"],
float_spread=0,
)
eur_irs = IRS(
effective=dt(2023, 1, 1),
termination="3M",
frequency="A",
notional=-1e6,
currency="eur",
payment_lag=0,
curves=["eureur", "eurusd"],
fixed_rate=0.0
)
usd_irs = IRS(
effective=dt(2023, 1, 1),
termination="3M",
frequency="A",
notional=1.1e6,
currency="usd",
payment_lag=0,
curves=["usdusd", "usdusd"],
fixed_rate=1.539463
)
pf = Portfolio([xcs, eur_irs, usd_irs])
pf.cashflows_table(solver=solver)
These are exactly the opposite of the underlying FXSwap
so when we combine everything, we get a completely cashflow and delta neutral portfolio.
pf = Portfolio([xcs, eur_irs, usd_irs, fxs])
pf.delta(solver=solver).style.format(precision=4)
4) Conclusion
By showing each stage we assert that 2 FXExchanges
completely replicates 2 single period fixed-float IRS
in each currency and a single period non-mtm float-float cross-currency basis swap (NonMtmXCS
).
The pricing parameters on all instruments have been fixed to net out all the cashflows (the FXExchanges
are at mid market and value to zero).
A string of these such combinations could be used to replicate swaps with more than a single period.