Table of Contents

Basis swaps and FX

A basis swap between two Euribor tenors, an FX forward, and a curve implied by FX swap points, all on the stable API; then, experimental, a curve from cross-currency basis swaps. The market is flat: €STR at 2.80%, dollars at 4.00%, and EUR/USD at 1.0850.

A basis swap

Euribor 3M plus a spread against Euribor 6M, each forecast on its own curve and both discounted on €STR, as a FloatFloatSwap. A spread is linear in the 3M leg's coupons, so the leg's BPS gives the fair spread:

var euribor3M = new Euribor3M(new FlatForward(context.EvaluationDate, 0.0295, new Actual365Fixed()));
var euribor6M = new Euribor6M(new FlatForward(context.EvaluationDate, 0.0305, new Actual365Fixed()));
Calendar calendar = new TARGET();
Date start = calendar.Advance(context.EvaluationDate, 2, TimeUnit.Days);
Date end = calendar.Advance(start, 5, TimeUnit.Years, BusinessDayConvention.ModifiedFollowing);
Schedule Quarterly() => new MakeSchedule().From(start).To(end).WithTenor(euribor3M.Tenor).WithCalendar(calendar)
    .WithConvention(BusinessDayConvention.ModifiedFollowing).ToSchedule();
Schedule Semiannual() => new MakeSchedule().From(start).To(end).WithTenor(euribor6M.Tenor).WithCalendar(calendar)
    .WithConvention(BusinessDayConvention.ModifiedFollowing).ToSchedule();
FloatFloatSwap Swap(double spread) => new(SwapType.Payer, 10e6, 10e6, Quarterly(), euribor3M, new Actual360(),
    Semiannual(), euribor6M, new Actual360(), context, spread1: spread);

SwapResults flat = DiscountingSwapEngine.Calculate(Swap(0.0), context, EurDiscount);
// A spread of one basis point on the 3M leg moves that leg's NPV by its BPS.
double fairSpread = -flat.NPV!.Value / flat.LegBPS[0]!.Value * 1e-4;
SwapResults atFair = DiscountingSwapEngine.Calculate(Swap(fairSpread), context, EurDiscount);
3M + spread against 6M Spread NPV
Flat 0.00 bp 47,324.97
At the fair spread 10.0320 bp 0.00

An FX forward

Euros sold for dollars in a year, valued from the two discount curves and spot; struck at its fair rate, it is worth nothing:

Date maturity = new TARGET().Advance(context.EvaluationDate, 1, TimeUnit.Years);
var probe = new FxForward(10e6, new EURCurrency(), new USDCurrency(), Spot, maturity, true);
double fair = DiscountingFxForwardEngine.Calculate(probe, context, EurDiscount, UsdDiscount, Spot).FairForwardRate!.Value;

var forward = new FxForward(10e6, new EURCurrency(), new USDCurrency(), fair, maturity, true);
FxForwardResults result = DiscountingFxForwardEngine.Calculate(forward, context, EurDiscount, UsdDiscount, Spot);
One-year EUR/USD forward Value
Spot 1.0850
Fair forward rate 1.098026
NPV of €10 million sold forward at that rate, in dollars 0.00

A curve from FX swap points

A euro curve, collateralized in dollars, bootstrapped from EUR/USD swap points with FxSwapRateHelper:

(Period Tenor, double Points)[] quotes =
[
    (new Period(1, TimeUnit.Months), 0.0010), (new Period(3, TimeUnit.Months), 0.0031),
    (new Period(6, TimeUnit.Months), 0.0062), (new Period(1, TimeUnit.Years), 0.0125),
];
var helpers = quotes.Select(q => new FxSwapRateHelper(q.Points, Spot, q.Tenor, 2, new TARGET(),
    BusinessDayConvention.Following, false, false, UsdDiscount, context)).ToList();
var eurCurve = new PiecewiseYieldCurve<Discount, LogLinear, IterativeBootstrap>(
    context.EvaluationDate, helpers, new Actual365Fixed(), context);

The curve gives back each quote:

Tenor Swap points Implied by the curve
1M 10.00 10.00
3M 31.00 31.00
6M 62.00 62.00
1Y 125.00 125.00

Experimental: a curve from cross-currency basis swaps

The cross-currency basis-swap helpers are experimental, as QuantLib's are: their members can change in any release. Using them needs the opt-in, NQL0001 in C# (here a #pragma in the sample's own file) and import nanoql.experimental in Python (concepts). A euro curve bootstrapped from EUR/USD basis swaps on €STR and SOFR, collateralized in dollars:

var estr = new Estr(BasisAndFx.EurDiscount);
var sofr = new Sofr(BasisAndFx.UsdDiscount);
(int Years, double Basis)[] quotes = [(1, -0.0008), (2, -0.0010), (3, -0.0012), (5, -0.0013)];
var helpers = quotes.Select(q => new ConstNotionalCrossCurrencyBasisSwapRateHelper(
    q.Basis, new Period(q.Years, TimeUnit.Years), 2, new TARGET(), BusinessDayConvention.ModifiedFollowing, false,
    estr, sofr, BasisAndFx.UsdDiscount, isFxBaseCurrencyCollateralCurrency: false,
    isBasisOnFxBaseCurrencyLeg: true, context, paymentFrequency: Frequency.Quarterly)).ToList();
var eurCurve = new PiecewiseYieldCurve<Discount, LogLinear, IterativeBootstrap>(
    context.EvaluationDate, helpers, new Actual365Fixed(), context);
Tenor Quoted basis Implied by the curve
1Y -8.00 bp -8.00 bp
2Y -10.00 bp -10.00 bp
3Y -12.00 bp -12.00 bp
5Y -13.00 bp -13.00 bp