Esc

    The API is under testing and may change before release.

    FX swaps

    This page lists what you need to book and value an FX swap: the constructor's arguments, how it's priced, and an example you can run. An FX swap exchanges two currencies now and exchanges them back later at rates agreed today, so it works as a loan in one currency secured by the other.

    Quick example

    The example reads its quotes from the desk sheets; load them once and it runs as shown.

    This reads the near and far forwards on EUR/USD and the swap points between them, then books a swap that buys a million euros in October 2026 at the market and sells them back a year later at 1.13. It values the swap, adds up its two legs on their own, and strikes a second swap at both of the market's forwards.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{EUR_CLOSE, SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, CollateralSpec, Currency, CurrencyPair, FxForward, FxRates, FxSwap,
        MarketContext, QuoteSheet, YieldCurve,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let spot = 1.1000;
        let (near_date, far_date) = (date((2026, 10, 5)), date((2027, 10, 5)));
        let notional = 1_000_000.0;
        let (eur, usd): (Currency, Currency) = ("EUR".parse()?, "USD".parse()?);
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(&CollateralSpec::Currency(usd), &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?)?
            .with_recipe_quotes(&CollateralSpec::Currency(eur), &QuoteSheet::from_rows(EUR_CLOSE.iter().copied())?)?
            .with_fx_rates(FxRates::from_quotes(&[(CurrencyPair::new(eur, usd), spot)])?);
        // Covered interest parity to `on`: S × P_EUR / P_USD.
        let forward = |on: NaiveDate| -> Outcome<f64> {
            let (eur_curve, _) = market.curve(&"discount(EUR)".parse()?).expect("EUR");
            let (usd_curve, _) = market.curve(&"discount(USD)".parse()?).expect("USD");
            Ok(spot * eur_curve.view::<f64>().discount(on)? / usd_curve.view::<f64>().discount(on)?)
        };
    
        // The swap points are the far forward less the near, in dollars per euro.
        let (near_rate, far_rate) = (forward(near_date)?, forward(far_date)?);
        println!("near forward  {near_rate:.6}");
        println!("far forward   {far_rate:.6}");
        println!("swap points   {:.2} pips", (far_rate - near_rate) * 10_000.0);
    
        // Buy a million euros near at the market and sell them back far at 1.13.
        let near = FxForward::new(eur, usd, notional, near_rate, near_date);
        let far = FxForward::new(eur, usd, -notional, 1.13, far_date);
        let swap = AnyInstrument::FxSwap(FxSwap::new(near.clone(), far.clone())?);
        let npv = swap.npv(&market)?;
        let legs = AnyInstrument::FxForward(near.clone()).npv(&market)?
            + AnyInstrument::FxForward(far).npv(&market)?;
        // Selling euros forward above the market's far forward earns the difference.
        println!("npv (USD)     {npv:.2}");
        println!("sum of legs   {legs:.2}");
    
        // Struck at both of the market's forwards, the swap is worth nothing.
        let at_market = AnyInstrument::FxSwap(FxSwap::new(
            near,
            FxForward::new(eur, usd, -notional, far_rate, far_date),
        )?);
        println!("at market     {:.2}", at_market.npv(&market)?.abs());
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import EUR_CLOSE, SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    SPOT = 1.1000
    NEAR, FAR = dt.date(2026, 10, 5), dt.date(2027, 10, 5)
    NOTIONAL = 1_000_000.0
    
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_recipe_quotes("EUR", EUR_CLOSE)
        .with_fx_rates([("EUR", "USD", SPOT)])
    )
    
    def forward(on: dt.date) -> float:
        """Covered interest parity to `on`: S × P_EUR / P_USD."""
        return SPOT * market.curve("discount(EUR)").discount(on) / market.curve("discount(USD)").discount(on)
    
    # The swap points are the far forward less the near, in dollars per euro.
    near_rate, far_rate = forward(NEAR), forward(FAR)
    print(f"near forward  {near_rate:.6f}")
    print(f"far forward   {far_rate:.6f}")
    print(f"swap points   {(far_rate - near_rate) * 10_000:.2f} pips")
    
    # Buy a million euros near at the market and sell them back far at 1.13.
    near = hlx.products.fx_forward("EUR", "USD", base_amount=NOTIONAL, contract_rate=near_rate, value_date=NEAR)
    far = hlx.products.fx_forward("EUR", "USD", base_amount=-NOTIONAL, contract_rate=1.13, value_date=FAR)
    swap = hlx.products.fx_swap(near, far)
    npv = swap.npv(market)
    legs = near.npv(market) + far.npv(market)
    # Selling euros forward above the market's far forward earns the difference.
    print(f"npv (USD)     {npv:.2f}")
    print(f"sum of legs   {legs:.2f}")
    
    # Struck at both of the market's forwards, the swap is worth nothing.
    at_market = hlx.products.fx_swap(
        near,
        hlx.products.fx_forward("EUR", "USD", base_amount=-NOTIONAL, contract_rate=far_rate, value_date=FAR),
    )
    print(f"at market     {abs(at_market.npv(market)):.2f}")
    near forward  1.100241
    far forward   1.126932
    swap points   266.91 pips
    npv (USD)     2935.85
    sum of legs   2935.85
    at market     0.00

    Selling the euros back at 1.13, above the market's far forward, earns the difference, and the swap is worth exactly the sum of its legs. Struck at the market's two forwards, it's worth nothing.

    How it's priced

    An FX swap is two forwards on one pair with opposite notionals. With FF the covered-interest-parity forward, buying NN base on T1T_1 at K1K_1 and selling it on T2T_2 at K2K_2 is worth

    V=N (F(T1)−K1) Pquote(T1)  −  N (F(T2)−K2) Pquote(T2).V = N\,(F(T_1) - K_1)\,P_{\text{quote}}(T_1) \;-\; N\,(F(T_2) - K_2)\,P_{\text{quote}}(T_2).

    The swap points are F(T2)−F(T1)F(T_2) - F(T_1). Why they carry the interest rate differential is covered in how FX products work.

    API

    Argument Type Default Meaning
    products.fx_swap(near, far) two Instruments required the near and far legs, each an products.fx_forward on the same pair; Rust FxSwap::new(near, far)?
    near FX forward required the first exchange, usually at spot
    far FX forward required the exchange back, with the opposite base_amount
    Symbol Meaning Rust Python
    near and far legs two forwards with opposite notionals FxForward::new, twice products.fx_forward, twice
    the swap the two legs as one instrument FxSwap::new(near, far) products.fx_swap(near, far)
    VV the swap's value AnyInstrument::npv Instrument.npv
    F(T)F(T) the forward to each date S Pbase/PquoteS\,P_{\text{base}}/P_{\text{quote}} from the market's curves S Pbase/PquoteS\,P_{\text{base}}/P_{\text{quote}} from the market's curves

    Defaults and limits

    • The market: a discount curve for each currency and the pair's spot, as for a forward.
    • The legs: each leg is valued as the forward it is, so the swap is worth their sum. A leg whose value date has passed is worth nothing.
    • Currency of the value: the npv is in the quote currency.
    • Quoting: the market quotes the swap points, the far rate less the near, in pips. Each pair's spot lag and calendars are in the FX conventions catalog.

    See also

    • Source: crates/helixrs/src/instruments/fx.rs
    • Related pages:
      • How FX products work, the ideas behind the swap.
      • FX forwards, for one leg on its own.
      • Cross-currency calibration, for FX swaps as curve rungs.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.