I need to be able to create a simple FX Forward curve from spot and FXSwap rates and use that for pricing a FXF as opposed to pricing off the foreign and domestic discount curves. Due to how our USD discount curve is constructed, reconstructing the forward discount curves from FXS under a multi-currency bootstrapping is resulting in pricing discrepancies. Since we are also missing an OIS basis adjustment, the interpolated FXSwap rate off the foreign and domestic curves can be as high as 5-7 pips away from the FX swap rate taken from market data.

What I would like to do is create a simple FXF curve that can be extracted from at builtMarketData object. Does something like this exist in Strata? It seems that the CurveGroup interface only supports Rate curves? Which means that if I create a CurveDefinition.csv for these the assumption is that they would bootstrapped into Rate curves?

Also, I assume I would want to create an instance of an InterpolatedNodalCurve using FxSwapCurveNode as the curve points?

For the calibration including FX forward, an example is in “CalibrationXCcyCheckExample” in the strata-examples module. The example contains also cross-currency swaps, but if you strip the example with only the FX Swap nodes, you should obtain an example for FX market.

The calibration result is, as you say, two discount curves. One based on single currency instruments (USD OIS in the example) and the other based on the FX swaps (EUR in the example). By the calibration process, the FX swaps are repriced exactly to the market that was used for the calibration. I’m surprised that you get results that are 5-7 pips away from the market if you have used the market rates as inputs.

The approach of the example is correct for FX swaps collateralised in the first currency (USD in the example) and interest paid on the collateral equal to the overnight benchmark used in the OIS (Fed Funds). Is this the framework in which you are modelling?

After the calibration, the ImmutableRatesProvider can return a FxForwardRates, which is an object providing the forward FX rates at any (forward) date. It would be possible to define a FxForwardRates which instead of being based on two discount curves (like our DiscountFxForwardRates) is based on one discount curve plus a set of FX points and do the interpolation on the FX points. To our experience the difference is not very large and working on FX points, which are not annualised in the market, can be tricky. Doing it would require defining extra objects to be used in the calibration process.

The approach of the example is correct for FX swaps collateralised in the first currency (USD in the example) and interest paid on the collateral equal to the overnight benchmark used in the OIS (Fed Funds). Is this the framework in which you are modelling

The short answer is yes, I’m attempting to model the calibrations in this way.

I don’t have interest rates for foreign currencies to compare, only the USD fed funds OIS out to 50 years and FXS quotes from Tullet/Prebon for 15 different currencies starting at 7d and varying from 24m to 5Y out. After calibration the forward rates returned by rate provider are not able to price an FXF exactly to par. There is a residual amount that varies by currency. On say a 1mm USD/CAD ATM 6m forward I get an NPV of at most ± $100 (if the market is moving around a lot, i.e. presumably the OIS curve is out of sync with the FXS quotes ) most often it is around ±$10. But it’s never 0. I would expect that if I pick say the 6M point to compare that the Forward - Spot (as obtained from the rates provider post calibration) would equal the FXS quote that I received from Tullet with that same tenor with an epsilon of 1e-5 at least.

I wonder if the source of error is the reconstructed foreign currency discount curves? I’m assuming an NPV = DF_c * (N_b * F - N_c) where the sources of error are DF_c and F.

The calibrated rates are a comparison against BB. However, I noticed the mid-market FXS rate from Tullet can differ from BB by as much as 1 or 2 pips so an additional 2-3 pips on top of that is where I derived my worst case of 5-7 pips.

I can provide curve definitions and a Joda-Beans JSON dump of the builtMarketData if that would help.