Building Cross-Currency Curves to Price Float-Float Swaps
Summary
The document presents a curve-building setup intended to help benchmark a spreadsheet model for float-float cross-currency swaps. The question describes importing GBP, USD, and EUR par rates and cross-currency basis quotes, interpolating rates, bootstrapping discount factors, and deriving FX forwards and projected coupon forwards. The reported discrepancy is concentrated in long tenors, especially in the final notional cashflow present value, while fixed-fixed swap prices and many other comparisons are close to a market system’s values.
The answer begins an alternative implementation outline: construct local GBP and USD interest-rate curves from swap instruments, add a GBP-USD cross-currency curve calibrated to basis swaps, then derive FX forwards and a proxy curve. This offers a way to structure an independent benchmark, but the supplied material ends before the calibration workflow is complete or the long-tenor discrepancy is diagnosed. It gives no validated correction, pricing results, or explanation of whether interpolation, conventions, or discounting caused the mismatch.
Key ideas
- Local currency curves can be bootstrapped from interest-rate swap quotes.
- A cross-currency basis curve can be calibrated alongside the local curves.
- FX forwards connect the currency curves for discounting and projection.
- A large final notional present-value difference can point toward discount-curve issues.
- The outlined benchmark is incomplete and does not resolve the reported long-tenor pricing discrepancy.
Tags
Full text
# Pricing a float-float cross currency swap
# Pricing a float-float cross currency swap
I've come across a strange problem when pricing a float-float cross currency swap in excel. The calculated mid spread matches closely with Bloomberg's SWPM for all tenors (<1bp difference) except around 20 year tenor for spot and forward starting swaps (up to 5bp difference). So from 17 year tenor, the difference starts creeping up from ~2bp away from SWPM up to 5bp away from SWPM at 20 year tenor and back down to 2bp at 23 year tenor.
The excel spreadsheet starts with importing par rates for SONIA, SOFR, and ESTR along with the basis for the 3 currencies up to 50y from Bloomberg. I interpolate the quarterly compounded par rates (since xccy basis is quarterly compounded) at every 0.25y using cubic spline and bootstrap the discount factors to get the risk free discount factors in the respective currencies. Now to get the discount factors under GBP cash CSA, I first get the GBP discount factors under USD and EUR CSA (bootstrapping done using SONIA forward rates) by including the xccy basis and using those discount factors, calculate the FX forward rates at each point which is used to calculate the USD and EUR discount factors under GBP cash CSA. Day count convention is as per the currency (ACT/360 for USD and EUR, ACT/365 for GBP)
To calculate the forward rates in each currency for the projected coupons, I follow Bloomberg's methodology where I calculate continuously compounded zero rates from bootstrapped discount factors and then get the continuously compounded forward rates (zero rates and fwd rates are using ACT/365 convention regardless of currency conventions).
While SWPM is using constant forward interpolation, I doubt that would cause such a large mismatch. Additionally, the method outlined above of calculating discount factors is also used to price fixed-fixed XCCY and it always matches closely with SWPM (<1bp). I've also compared the forward rates with SWPM and the differences oscillate around 0 (including PVs). The only thing I've noticed is that the final cashflow (notional) PV is very different (>100k for 10mm notional) to SWPM indicating that discount factors are the culprit here but given it works when pricing a fixed-fixed xccy, I don't see how this can be the issue.
Given that this method works for most tenors across the curve and currency pairs (GBPUSD, GBPEUR), what is causing this method to not work between 17y-23y? Additional questions would be
- Are my discount factors incorrectly calculated?
- Are my forward rates incorrectly calculated
- Something else I may have gotten wrong or that I haven't thought of
Bloomberg's helpdesk is of no help as they don't provide workouts even though I shared my pricing file with them asking them to let me know where I might be going wrong rather than explicitly let me know what SWPM is doing.
## Answer by Attack68 (score 2)
https://quant.stackexchange.com/a/82304
As well as my comments if you want to benchmark your sheet I'll show you the Python code to setup the first 3 data points on your curves give the data you saved down to your file.
#### Setup local rates curves in GBP and USD
```
from rateslib import * # Python 3.12, rateslib 1.7.0
gbp = Curve(
nodes={
dt(2025, 4, 4): 1.0,
dt(2026, 4, 4): 1.0,
dt(2027, 4, 4): 1.0,
dt(2028, 4, 4): 1.0,
},
convention="act365f",
calendar="ldn",
id="sonia",
)
usd = Curve(
nodes={
dt(2025, 4, 4): 1.0,
dt(2026, 4, 4): 1.0,
dt(2027, 4, 4): 1.0,
dt(2028, 4, 4): 1.0,
},
convention="act360",
calendar="nyc",
id="sofr",
)
rates_solver = Solver(
curves=[gbp, usd],
instruments=[
IRS(dt(2025, 4, 4), "1y", spec="gbp_irs", curves="sonia"),
IRS(dt(2025, 4, 4), "2y", spec="gbp_irs", curves="sonia"),
IRS(dt(2025, 4, 4), "3y", spec="gbp_irs", curves="sonia"),
IRS(dt(2025, 4, 8), "1y", spec="usd_irs", curves="sofr"),
IRS(dt(2025, 4, 8), "2y", spec="usd_irs", curves="sofr"),
IRS(dt(2025, 4, 8), "3y", spec="usd_irs", curves="sofr"),
],
s=[4.0146, 3.8208, 3.7579, 3.7011, 3.3946, 3.3175]
)
```
#### Add a cross-currency curve: GBPUSD and create an FX forwards object
```
gbpusd = Curve(
nodes={
dt(2025, 4, 4): 1.0,
dt(2026, 4, 4): 1.0,
dt(2027, 4, 4): 1.0,
dt(2028, 4, 4): 1.0,
},
convention="act365f",
calendar="ldn",
id="gbpusd",
)
fxf = FXForwards(
fx_curves={"gbpgbp": gbp, "usdusd": usd, "gbpusd": gbpusd},
fx_rates=FXRates({"gbpusd": 1.29}, settlement=dt(2025, 4, 8))
)
basis_solver = Solver(
fx=fxf,
pre_solvers=[rates_solver],
curves=[gbpusd],
instruments=[
XCS(dt(2025, 4, 8), "1y", spec="gbpusd_xcs", curves=["sonia", "gbpusd", "sofr", "sofr"]),
XCS(dt(2025, 4, 8), "2y", spec="gbpusd_xcs", curves=["sonia", "gbpusd", "sofr", "sofr"]),
XCS(dt(2025, 4, 8), "3y", spec="gbpusd_xcs", curves=["sonia", "gbpusd", "sofr", "sofr"]),
],
s=[1.125, 0, -1.375]
)
```
#### Derive the USDGBP curve via proxy with FX forwardsShown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.