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.
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(())
}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 the covered-interest-parity forward, buying base on at and selling it on at is worth
The swap points are . 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) |
| the swap's value | AnyInstrument::npv |
Instrument.npv |
|
| the forward to each date | from the market's curves | 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.