FX Spot Rates#

This documentation page discusses the methods of the FXRates class which are summarised below:

rateslib.fx.FXRates(fx_rates[, settlement, base])

Object to store and calculate FX rates for a consistent settlement date.

rateslib.fx.FXRates.rate(pair)

Return a specified FX rate for a given currency pair.

rateslib.fx.FXRates.rates_table()

Return a DataFrame of all FX rates in the object.

rateslib.fx.FXRates.convert(value, domestic)

Convert an amount of a domestic currency into a foreign currency.

rateslib.fx.FXRates.convert_positions(array)

Convert an array of currency cash positions into a single base currency.

rateslib.fx.FXRates.positions(value[, base])

Convert a base value with FX rate sensitivities into an array of cash positions.

rateslib.fx.FXRates.update([fx_rates])

Update all or some of the FX rates of the instance with new market data.

rateslib.fx.FXRates.restate(pairs[, keep_ad])

Create a new FXRates class using other (or fewer) currency pairs as majors.

rateslib.fx.FXRates.to_json()

Convert FXRates object to a JSON string.

rateslib.fx.FXRates.from_json(fx_rates, **kwargs)

Load an FXRates object from a JSON string.

Introduction#

FXRates classes are initialised straightforwardly with a given set of FX rates. The optional settlement argument is only used in conjunction with FXForwards specification. The cross multiplications to derive all FX rates from the stated FX market are performed internally.

In [1]: fxr = FXRates(
   ...:     fx_rates={"eurusd": 1.1, "gbpusd":1.25, "eursek": 10.85, "noksek": 1.05},
   ...:     base="USD"
   ...: )
   ...: 

In [2]: fxr.rates_table()
Out[2]: 
     eur  gbp   nok  usd   sek
eur 1.00 0.88 10.33 1.10 10.85
gbp 1.14 1.00 11.74 1.25 12.33
nok 0.10 0.09  1.00 0.11  1.05
usd 0.91 0.80  9.39 1.00  9.86
sek 0.09 0.08  0.95 0.10  1.00

The FXRates class is also used when one has an Instrument in one currency but the results of calculations are preferred in another, say, when accounting currency is something other than the currency of underlying Instrument. For example, below we create a Curve and a EUR IRS, and calculate some metrics expressed in that currency.

In [3]: curve = Curve(
   ...:     nodes={dt(2022, 1, 1): 1.0, dt(2022, 7, 1): 0.99, dt(2023, 1, 1): 0.97},
   ...:     id="estr"
   ...: )
   ...: 

In [4]: swap = IRS(dt(2022, 1, 1), "1Y", "A", fixed_rate=2.00, currency="EUR")

In [5]: swap.npv(curve)
Out[5]: 10328.264125136171

In [6]: swap.analytic_delta(curve)
Out[6]: 98.32540773069167

The FXRates class defined above can be used to directly return the above metrics in the specified base currency (USD).

In [7]: swap.npv(curve, fx=fxr)
Out[7]: <Dual: 11361.090538, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [10328.3, 0.0, 0.0, ...]>

In [8]: swap.analytic_delta(curve, fx=fxr)
Out[8]: <Dual: 108.157949, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [98.3, 0.0, 0.0, ...]>

In [9]: swap.cashflows(curve, fx=fxr).transpose()
Out[9]: 
                           leg1                 leg2
                              0                    0
Type                FixedPeriod          FloatPeriod
Period                  Regular              Regular
Ccy                         EUR                  EUR
Acc Start   2022-01-01 00:00:00  2022-01-01 00:00:00
Acc End     2023-01-01 00:00:00  2023-01-01 00:00:00
Payment     2023-01-03 00:00:00  2023-01-03 00:00:00
Convention               ACT360               ACT360
DCF                        1.01                 1.01
Notional             1000000.00          -1000000.00
DF                         0.97                 0.97
Collateral                 None                 None
Rate                       2.00                 3.05
Spread                      NaN                 0.00
Cashflow              -20277.78             30927.84
NPV                   -19665.08             29993.35
FX Rate                    1.10                 1.10
NPV Ccy               -21631.59             32992.68

Or, other currencies too, that are non-base, can also be displayed upon request.

In [10]: swap.npv(curve, fx=fxr, base="NOK")
Out[10]: <Dual: 106725.395960, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [0.0, 0.0, -101643.2, ...]>

Sensitivity Management#

This object does not only create an FX rates_table(), it also performs calculations and determines sensitivities, using automatic differentiation, to the FX rates that are given as the parameters in the construction. For example, in the above construction the EURSEK and NOKSEK rates are given, as majors. The EURNOK exchange rate, is a cross, and being derived from those means it will demonstrate that dependency to those two, whilst the EURSEK rate will demonstrate only direct one-to-one dependency with the quoted EURSEK rate.

In [11]: fxr.rate("eursek")
Out[11]: <Dual: 10.850000, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [0.0, 0.0, 0.0, ...]>

In [12]: fxr.rate("eurnok")
Out[12]: <Dual: 10.333333, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [0.0, 0.0, -9.8, ...]>

In a similar manner cashflows, that are converted from one currency to another also maintain sensitivity calculations stored within their Dual number specification.

In [13]: sek_value = fxr.convert(100, "eur", "sek")

In [14]: sek_value
Out[14]: <Dual: 1085.000000, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [0.0, 0.0, 0.0, ...]>

Interpreting Dual Values#

The above value has an “fx_eursek” dual value of 100 (SEK). This means that for the EURSEK rate to increase by 1.0 from 10.85 to 11.85 the base (SEK) value would increase by 100, from 1,085 SEK to 1,185 SEK. In this case this is exact, but the figure of “100” represents an instantaneous derivative. When dealing with reverse exposures (i.e SEKEUR) this becomes apparent.

In [15]: eur_value = fxr.convert(1085, "sek", "eur")

In [16]: eur_value
Out[16]: <Dual: 100.000000, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [-0.0, -0.0, -0.0, ...]>

Now when EURSEK increases to 11.85 the new “eur_value” would actually be 91.56 EUR. This is not (100-9.2166=) 90.78. But this sensitivity is applicable on an infinitesimal basis.

Conversion Methods#

By interpreting and storing values with FX sensitivities the underlying true positions are maintained. A 100 EUR cash position valued as 1,085 SEK, is not the same as a 1,085 SEK cash position when considering financial risk exposures. Therefore the methods convert(), convert_positions() and positions() exist to seamlessly transition between the different representations.

In [17]: cash_positions = fxr.positions(sek_value, base="sek")

In [18]: cash_positions
Out[18]: 
eur   100.00
gbp     0.00
nok     0.00
usd     0.00
sek     0.00
dtype: float64

And the cash positions can be converted into any base representation currency.

In [19]: eur_value = fxr.convert_positions(cash_positions, base="eur")

In [20]: eur_value
Out[20]: <Dual: 100.000000, (fx_eurusd, fx_gbpusd, fx_noksek, ...), [0.0, 0.0, 0.0, ...]>

Updating#

Once an FXRates class has been instantiated it may then be associated with other objects, such as an FXForwards class.

Note

It is best practice not to create further FXRates instances but to update the existing ones instead. Please review the documentation for update() for further details.