FX Spot Rates#
This documentation page discusses the methods of the
FXRates
class which are summarised below:
|
Object to store and calculate FX rates for a consistent settlement date. |
|
Return a specified FX rate for a given currency pair. |
Return a DataFrame of all FX rates in the object. |
|
|
Convert an amount of a domestic currency into a foreign currency. |
Convert an array of currency cash positions into a single base currency. |
|
|
Convert a base value with FX rate sensitivities into an array of cash positions. |
|
Update all or some of the FX rates of the instance with new market data. |
|
Create a new |
Convert FXRates object to a JSON string. |
|
|
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.