Skip to content
All library documents

FX Swap Curve Construction and Spot-Date Alignment

Article Quant Q&A · Author: user35980

Summary

The document explains a QuantLib FX swap curve setup using forward-point quotes, a spot exchange rate, and a collateral discount curve. It describes a mismatch between input forward points and values reconstructed from the resulting curve when the curve and collateral curve start on the trade date despite the FX spot settlement lag.

The proposed fix is to align the curve’s reference date and the collateral curve’s start date with the spot settlement date. The example uses a flat collateral rate and several FX swap maturities, then compares reconstructed forward points before and after adjusting for discounting to the spot date. This is a practical implementation note rather than a general derivation; the discussion is limited to the shown QuantLib setup and does not establish whether other configurations behave the same way.

Key ideas

  • FX forward points are the difference between the forward exchange rate and the spot rate.
  • The example builds an FX curve from swap forward-point quotes and a collateral curve.
  • A mismatch can arise when the curve and collateral curve use different effective date conventions relative to spot settlement.
  • Starting both curves on the FX spot date is presented as a way to align the reconstructed forward points.

Tags

Full text
# fxSwapRateHelper class in quantlib


# fxSwapRateHelper class in quantlib












In accordance with the definition of an FX forward $F$ of a spot rate $S$, $$ F=S\frac{(1+r_d\tau_d)}{(1+r_f\tau_f)} $$ (for domestic, foreign rates $r_d,r_f$ and day counts $\tau_d,\tau_f$) so that forward points are given by $F-S$.

Using the `fxSwapRateHelper` class in quantlib, the below code should return the inputted forward points to build the curve but it seems to require a spot lag rate adjustment to get them back. Perhaps I'm messing up something here but is this implementation deliberate or a bug in the class?

```

# Trade date setup
trade_date = ql.Date(3, 1, 2025)
spot_lag=2
ql.Settings.instance().evaluationDate = trade_date
calendar = ql.UnitedStates(0)
spot_sett=calendar.advance(trade_date,spot_lag,ql.Days)
business_convention_base = ql.ModifiedFollowing
end_of_month = False

# Spot FX rate
spot_rate = ql.SimpleQuote(1.2000)
spot_handle = ql.QuoteHandle(spot_rate)

# Collateral curve
collateral_curve = ql.YieldTermStructureHandle(ql.FlatForward(trade_date, ql.QuoteHandle(ql.SimpleQuote(0.03)), ql.Actual360()))

# FX swap fwd point quotes
fxswap_quotes = [ql.SimpleQuote(0.004), ql.SimpleQuote(0.01), ql.SimpleQuote(0.027)]
fxswap_handles = [ql.QuoteHandle(q) for q in fxswap_quotes]
# Spot lag and maturities
dffxswapsFXspotlag = [spot_lag]*3
dffxswapsmaturity = [ql.Period(1, ql.Months), ql.Period(3, ql.Months), ql.Period(6, ql.Months)]

# Create FxSwapRateHelper objects
fxSwapHelpers = [
    ql.FxSwapRateHelper(
        quote,
        spot_handle,
        maturity,
        int(spot_lag),
        calendar,
        business_convention_base,
        end_of_month,
        True,  # Base currency collateralized
        collateral_curve
    )
    for spot_lag, quote, maturity in zip(dffxswapsFXspotlag, fxswap_handles, dffxswapsmaturity)
]
curve =ql.PiecewiseLinearZero(0,calendar, fxSwapHelpers, ql.Actual360())

# should return inputted fwd points
for i,j in enumerate(curve.nodes()):
    print((spot_handle.value()*collateral_curve.discount(curve.nodes()[i][0])/curve.discount(curve.nodes()[i][0]))-spot_handle.value())
# spot lag rate adj gives back correct inputted points
spot_lag_adj=curve.discount(spot_sett)/collateral_curve.discount(spot_sett)
for i,j in enumerate(curve.nodes()):
    print((spot_lag_adj*spot_handle.value()*collateral_curve.discount(curve.nodes()[i][0])/curve.discount(curve.nodes()[i][0]))-spot_handle.value())
```

## Answer by Lorienzo (score 2)

https://quant.stackexchange.com/a/82335

Took a closer look, you had actually implemented spot_lag_adj to bring the correct inputted fwd points. So here are the things: either you have both curve build start on same date as FX Spot date as below code; otherwise there will be mismatch between discount factor on spot date vs curve build starting point.

```
collateral_curve = ql.YieldTermStructureHandle(ql.FlatForward(spot_sett, ql.QuoteHandle(ql.SimpleQuote(0.03)), ql.Actual360()))
curve =ql.PiecewiseLinearZero(spot_lag,calendar, fxSwapHelpers, ql.Actual360())
```

Shown 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.