# FX risk reversal approximation

i see this risk reversal approximation in Uwe Wystup's

in which the approximation of a risk reversal is given by:

vega of ATM x risk reversal in volatility terms

as of today, i see the following market pricing:

• FX spot 1.0727
• FX 3m fwd 1.0775,
• usd 3m depo 5.51,
• eur 3m depo 3.77,
• risk reversal 3 months exp/del 20/24 Sep, 25d,
• call strike 1.0998,
• put strike 1.0504,
• bid/offer 0.097% / 0.113% EUR,
• EUR 1 mio notional
• vanilla 3 months exp/del 20/24 Sep,
• call strike atms 1.0728,
• vol 6.7%/6.8%,
• bid/offer 1.55/1.57% eur,
• vega in eur is 1912,
• EUR 1 mio notional

off bloomberg, i see 3months, atm mid 6.6, 25d rr, -1.5 call 6.12 put 7.621

questions:

1. how is the cost of the risk reversal (in EUR%) approximated when its mentioned:

"cost of the risk reversal is approximated by the vega of the at-the-money vanilla times the risk reversal in terms of volatility"

1. are these approximations valid?

The approximation (15) begins with the quantity:

$$\underbrace{c(\sigma^+_\Delta) - p(\sigma_\Delta^-)}_{\text{mkt risk reversal premium}} - \underbrace{ \left(c(\sigma_0) + p(\sigma_0)\right )}_{\text{baseline risk reversal premium atm vol}}$$

The baseline risk reveral premium is required because an X-delta risk reversal does not have zero premium even for a flat curve, and the reason for that is that ATM-delta strike and the ATM-forward strike are not the same. That difference grows with vol and with time to expiry.

I will put some numbers on this for demonstration. First I will use your EUR, USD interest rates to construct interest rate curves and add your FX forward price to configure everything in a consistent framework:

# PYTHON: rateslib >= 1.3.0
from rateslib import *

eur = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 28): 1.0}, calendar="tgt")
usd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 28): 1.0}, calendar="nyc")
eurusd = Curve({dt(2024, 6, 20): 1.0, dt(2024, 9, 28): 1.0})
fxr = FXRates({"eurusd": 1.0727}, settlement=dt(2024, 6, 24))
fxf = FXForwards(
fx_rates=fxr,
fx_curves={"eureur": eur, "eurusd": eurusd, "usdusd": usd}
)
pre_solver = Solver(
curves=[eur, usd, eurusd],
instruments=[
IRS(dt(2024, 6, 24), "3m", spec="eur_irs", curves=eur),
IRS(dt(2024, 6, 24), "3m", spec="usd_irs", curves=usd),
FXExchange(pair="eurusd", settlement=dt(2024, 9, 24), curves=[None, eurusd, None, usd])
],
s=[3.77, 5.51, 1.0775],  # <- YOUR RATES
fx=fxf,
)
SUCCESS: func_tol reached after 8 iters, f_val: 3.92e-13, time: 0.0102s


Now we can get some option values. These are the FX points premium values (in USD) for each option in the 25D Risk Reversal. Notice that they do not net to zero, even though the RR price in vol points is zero (since both are priced with 6.6% vol):

fx_option_args = dict(
pair="eurusd",
expiry=dt(2024, 9, 20),
curves=[None, eurusd, None, usd],
delivery_lag=2,
payment_lag=dt(2024, 6, 20),
calendar="tgt|nyc",
)
FXCall(strike="25d", **fx_option_args).rate(vol=6.6, fx=fxf)
# <Dual: 52.273662, .., ..>
FXPut(strike="-25d", **fx_option_args).rate(vol=6.6, fx=fxf)
# <Dual: 54.042099, ..,..>


Next the assumption is to approximate with first order the value of the calls and puts relative to their equivalent prices.

$$c(\sigma_\Delta^+) \approx c(\sigma_0) + c_\sigma(\sigma_0) (\sigma_\Delta^+ - \sigma_0)$$ $$p(\sigma_\Delta^-) \approx p(\sigma_0) + p_\sigma(\sigma_0) (\sigma_\Delta^- - \sigma_0)$$

Plugging this in gets you the second line of (15). Since it is first order its accuracy deteriates as the market vol price diverge further from the ATM vol.

Is this a reasonable approximation in your case? Lets explore the Put which has the greatest discrepancy. The -25D Put with vol at 7.621% has the quantities:

FXPut(strike="-25d", **fx_option_args).rate(vol=7.621, fx=fxf)
# <Dual: 62.565252, .., ..>
FXPut(strike="-25d", **fx_option_args).analytic_greeks(vol=7.621, fx=fxf)
# strike: 1.0511201224444822
# vega_usd: <Dual: 1702.692382, .., ..>


The same option as priced with 6.6% vol gives the values:

FXPut(strike=1.0511201224444822, **fx_option_args).rate(vol=6.60, fx=fxf)
# <Dual: 45.732276, .., ..>
FXPut(strike=1.0511201224444822, **fx_option_args).analytic_greeks(vol=6.60, fx=fxf)
# vega_usd: <Dual: 1587.730467, .., ..>
# vomma_usd: <Dual: 134.5585, .., ..>


So now, to first order: $$45.732276 + 15.877304 * 1.021 = 61.943$$ Whilst to second order, including vomma: $$45.732276 + 15.877304 * 1.021 + 1.345585 * 1.021^2 / 2 = 62.644$$

So even this reasonably small deviation produces enough error to negate some processes. I suppose it depends what you are doing.

The additional assumption that is used in (15) to derive the final lines is that $$c_\sigma(\sigma_0)$$ and $$p_\sigma(\sigma_0)$$ are the same. It is not clear to me the author's intention with notion but, to me, to be consistent with the linear approximation in the first step, $$c(.)$$ and $$p(.)$$ must have strikes that are determined with the correct market volatilities. When the strikes are determined in this manner the vega quantities are symmetric, i.e. $$c_\sigma(\sigma^+_\Delta) = p_\sigma(\sigma_\Delta^-)$$ but it is not true that $$c_\sigma(\sigma_0) = p_\sigma(\sigma_0)$$

FXCall(strike=1.1003294194069992, **fx_option_args).analytic_greeks(vol=6.6, fx=fxf)
# vega_usd: <Dual: 1759.13, .., ..>
FXPut(strike=1.0511201224444822, **fx_option_args).analytic_greeks(vol=6.60, fx=fxf)
# vega_usd: <Dual: 1587.73, .., ..>


So I too struggle with the validity of the claim.