5
$\begingroup$

I have a list of deltas and their corresponding volatilities in an FX market but I want to go from delta to strike price. In this Question similar problem is being discussed

How can I calculate the strike price or implied volatility from a given delta?

The way I understand it, the strike price can be found like this: enter image description here

Is my approach correct? If yes; pleace help me understand the term, N(d_1), so I can proceed with the solve process?

Edit:
I basically want to create the volatility smile in (strike,vol)-graph from data found by Bloombergs OVDV function: enter image description here

So maybe there's a simplere way to do so

$\endgroup$
3
  • $\begingroup$ In Excel the function N(d1) is called NORMSDIST(). In many other languages there is such a function. In Python it;s norm.cdf() as part of Scipy. $\endgroup$
    – nbbo2
    Commented Aug 5, 2016 at 19:36
  • $\begingroup$ In R; is it dnorm() og pnorm()? $\endgroup$
    – Sanjay
    Commented Aug 5, 2016 at 19:38
  • 1
    $\begingroup$ It's pnorm() in R language. It's a "p" because it returns a probability (a number between 0 and 1). $\endgroup$
    – nbbo2
    Commented Aug 5, 2016 at 19:43

4 Answers 4

7
$\begingroup$

This is a little more complicated than the answer provided above since this is FX and the convention for determining the strike matters.

https://www.researchgate.net/publication/275905055_A_Guide_to_FX_Options_Quoting_Conventions

Most pairs take premium in the foreign (i.e. left hand side) currency. This means that you are paying for an option in the underlying - like paying for an IBM call option with IBM shares - and those shares can be viewed as part of the delta - as a result most pairs use the "include premium" convention. The details are in Wystup's paper and you should read it. The math is easy and it is nice to see everytrhing spelled out for you. The only pairs that "Exclude Premium" are EURUSD, GBPUSD, AUDUSD, NZDUSD - so these calculate delta in the usual way. Also in FX for BBG, the convention is to typically use spot delta for expiries less than a year and forward delta for expiries >= 1 year. Otherwise onlyvix's answer above is fine if you assume that the foreign risk free rate is 0.0%. The actual delta is $e^{-r_ft}N(d1)$ in the exclude premium case.

$\endgroup$
4
  • $\begingroup$ Thanks, @FinanceGuyThatCantCode. I am struggling with the Time parameter. It makes a huge difference whether I use days, months etc. If I use $T=1/12$ (years) for the 1 month options I get my points really really close the ATM than if I use $T=21$ (days). By looking at the example provided above, What's the correct way of measuring time $T$ $\endgroup$
    – Sanjay
    Commented May 22, 2017 at 13:37
  • 2
    $\begingroup$ @Sanjay - Bloomberg FX uses calendar days for their vol calculations. So ACT/365 is the convention - it might be ACT/ACT to handle leap years better, but I believe it is ACT/365. So, you need the (exact expiration date-today's date)/365. Calculating the expiration date from tenors is complicated actually. I coded it up once - I did not enjoy that. A mostly complete description of what to do is at: en.wikipedia.org/wiki/Foreign_exchange_date_conventions There are a couple other exceptions to the rules described here, but this will get you 95% of the way there. $\endgroup$ Commented May 22, 2017 at 13:48
  • $\begingroup$ So, for for 1 month option, the the time $T$ is approximately $T=(30/365)$ ? I am not sure if I have understood it correctly. $\endgroup$
    – Sanjay
    Commented May 22, 2017 at 17:25
  • 1
    $\begingroup$ in that case 1M is approximately 1/12. Or if you prefer, use the number of days in month/365 - i.e. 30/365 if the option starts in April and 31/365 if May. However, if you need real accuracy, then you will need all the holiday calendars and conventions as discussed in the link I provided above. $\endgroup$ Commented May 22, 2017 at 18:14
5
$\begingroup$

Just to skip to solution from the aforementioned paper:

For a volatility surface of Delta $\Delta$ vs volatility $\sigma$, we can calculate the strike $K$ with underlying $f$,$\phi$ is 1 for call, -1 for put and time to expiration $\tau$, which should be a year fraction of working days:

$K = fe^{-\phi N^{-1}(\phi\Delta)\sigma\sqrt{\tau}+\frac{1}{2}\sigma^{2}\tau}$

$\endgroup$
2
  • $\begingroup$ Is it correct to compute the f using the covered interest rate parity: f(CCY1rate, CCY2rate, spot, t) = spot * exp[(CCY1rate - CCY2rate)*t] or is it the ATM forward ? $\endgroup$ Commented Apr 1, 2022 at 8:06
  • $\begingroup$ good job and thank you, this verifies blackscholes. i checked and this is correct $\endgroup$
    – Alex M.M.
    Commented Apr 15, 2022 at 13:55
3
$\begingroup$

Since $ \Delta_K = N(d_1) $ use normal inverse function $ N^{-1} $ (e.g. NORM.INV in excel, norminv in matlab) to calculate $ d_1 = N^{-1} (\Delta_K) $ Then use algebra to solve for $K$

$\endgroup$
0
$\begingroup$

It seems your end goal is to get a vol associated with some strike and tenor. In Bloomberg, you can actually let the API do that "trick" for you, including all conventions (premium included / excluded; spot / fwd delta...) and settings you have on the terminal.

For the strike override to work, you 1st have to set the delta override to 0 because that delta is used by default and that is the way to exclude it in the API.

=BDP("EURUSD Curncy","SP_VOL_SURF_BID","VOL_SURF_EXPIRY_OVR","20221030","VOL_SURF_CALLPUT_OVR","C","REFERENCE_DATE","20220324","VOL_SURF_DELTA_OVR","0","VOL_SURF_STRIKE_OVR=1.25”)

This gives you for the date 2022-03-24 the EURUSD call IVOL for expiry on 2022-10-30 and a strike of 1.25.

You can also use tenors directly like so:

=BDP("GBPUSD Curncy","SP_VOL_SURF_MID","VOL_SURF_MTY_OVR=1D","VOL_SURF_CALLPUT_OVR=P","VOL_SURF_STRIKE_OVR=1.1","VOL_SURF_DELTA_OVR=0")

If you really want to do it yourself, you can look at this answer which shows in formula and code how Bloomberg solves for strike from delta. Bear in mind, this doesn't work if premium adjusted while the excel formula above just does everything correctly for you and pulls the correct IV for any strike and tenor you wish.

Since FX is all OTC, you can never be sure about the details unless you ask your broker. Generally though, in order to maintain liquidity there are a number of standardized conventions. Unfortunately, these conventions vary between currency pairs. Usually, vanilla EURUSD options are not premium adjusted.

If you have access to Bloomberg, you can check on OVDV - 92)Settings->Conventions. Wystup and Reiswich, 2009 also have an overview of the most commonly used conventions.

enter image description here

See the following screenshot from Bloomberg's OVML, where the 3rd screenshot has 25D as an input and solves for strike (the exact decimals are shown in white - when you hover over the value in the OVML screen).

enter image description here

The values are quick to replicate:

  • For CCY1CCY 2, you have Notional in CCY1 (20MM) and Premium in CCY2 (USD)
  • You have a deferred (forward) premium, therefore I use two dates (see here for an explanation)
  • all other inputs are given
  • The model is just standard Garman Kohlhagen
  • all inputs are provided in the question

In Julia, this looks as follows:

#load packages
using Distributions, Dates, DataFrames, PrettyTables
#define helper functions
ppf(x) = quantile(Normal(0.0, 1.0),x)
N(x) = cdf(Normal(0,1),x)

#define GK
function GK(F,K, days_to_expiry, days_to_delivery ,ccy1, ccy2,σ)
    d1 = ( log(F/K) +  0.5*σ^2*days_to_expiry/365 ) / (σ*sqrt(days_to_expiry/365))
    d2 = d1 - σ*sqrt(days_to_expiry/365)
    c  = exp(-ccy2*days_to_delivery/365)*(F*N(d1) - K*N(d2))
    δ_spot = exp(-ccy1*days_to_expiry/365) * N(d1)
    δ_fwd = N(d1)
  return c, δ_fwd, δ_spot
end

The inputs are all given, but the days to expiry and delivery are computed. I allow for hours to expiry but that is irrelevant here (the computed price is below the quoted, and delta would increase with increasing time).

# inputs 
s = 1.0615
pts = 60.1
fwd_scale = 10^4
f = s + pts / fwd_scale
println("Forward = $f")
k = 1.101
σ = 0.089
ccy1 = 0.0255008 #0.0255 # EUR
ccy2 = 0.0478 # USD

price_dt = Date(2023,3,16)
premium_dt = Date(2023,6,20)
expiry_dt = Date(2023,6,16)
delivery_dt = Date(2023,6,20)
hours =  0 #0.7115  allows to get more accurate pricing but more hours to expiry would be needed (increases delta)
days_to_expiry = (expiry_dt - price_dt).value + hours/24
days_to_delivery = (delivery_dt - premium_dt).value + hours/24
r1_cont = log(1+ccy1*days_to_expiry/360)/(days_to_expiry/365)
r2_cont = log(1+ccy2*days_to_expiry/360)/(days_to_expiry/365)

I am omitting PrettyTables formatting. Essentially, I compute strike for 25D according to Wystup and Reiswich, 2009 (omitting the call/put flag φ because we only care about calls here):

$$ K = fe^{-N^{-1}(e^{rf\tau} * \delta_{s})*\sigma* \sqrt{t} + \frac{1}{2}*\sigma^{2}*\tau }$$ or for forward delta: $$ K = fe^{-N^{-1}(\delta_{f})*\sigma* \sqrt{t} + \frac{1}{2}*\sigma^{2}*\tau }$$

δ = 0.25
# compute strike from delta 
k_25D    = f*exp((1/2)*σ^2*days_to_expiry/365 - ppf(δ*exp(r1_cont *days_to_expiry/365))*σ*sqrt(days_to_expiry/365))
# get option value for computed strike and quoted strike 
opt  = [GK(f, strike, days_to_expiry, days_to_delivery, r1_cont, r2_cont, σ) for strike in (k, k_25D)]
# get spot premium 
premium_dt_spot = Date(2023,3,20)
days_to_delivery_spot = (delivery_dt - premium_dt_spot).value + hours/24
opt2 = [GK(f, strike, days_to_expiry, days_to_delivery_spot, r1_cont, r2_cont, σ) for strike in (k, k_25D)];
Notional = 20_000_000
df = DataFrame("Strike" => [k, k_25D], 
                "Fwd Premium USD" => [opt[1][1]*Notional, opt[2][1]*Notional ], 
                "Spot Premium USD" => [opt2[1][1]*Notional ,opt2[2][1]*Notional ], 
                "Fwd Delta" => [opt[1][2]*100 , opt[2][2]*100 ], "Spot Delta" => [opt[1][3]*100, opt[2][3]*100] )

The output matches Bloomberg exactly:

enter image description here

Using the computed spot delta of 25.0124 returns the strike (1.101):

DataFrame("Delta" => [δ, opt[1][3]], "Strike Solved" => [k_25D, f*exp((1/2)*σ^2*days_to_expiry/365 - ppf(opt[1][3]*exp(r1_cont *days_to_expiry/365))*σ*sqrt(days_to_expiry/365))])

P.S. If it were premium included, you would not be able to use a closed form solution and would need to solve for strike numerically - for example, using Brent's root-finding method as suggested by Wystup and Reiswich.

However, since you directly want the Bloomberg values, you can simply use the API for a convenient solution that matches OVDV by design.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.