3
$\begingroup$

EDIT

based on the answer provided, I have followed, but still I cant match the bbg data with my calculations, could some advise how to match the bloomberg Price given the data? enter image description here

import numpy as np
import scipy.stats as ss
def BlackScholes(payoff, S0, K, T, r, sigma, q):
    d1 = (np.log(S0 / K) + (r - q + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    
    if payoff == "call":
        return S0 * np.exp(-q * T) * ss.norm.cdf(d1) - K * np.exp(-r * T) * ss.norm.cdf(d2)
    elif payoff == "put":
        return K * np.exp(-r * T) * ss.norm.cdf(-d2) - S0 * np.exp(-q * T) * ss.norm.cdf(-d1)

d = 92
h = 5
m = 10
y = 365
T = d/y + h/24/y + m/60/24/y

rr =   0.05277
q =0
S0 = 4739.21
K = 4740
sigma = 0.12954
print(BlackScholes(payoff='call', S0=S0, K=K, T=T, r=rr, sigma=sigma, q=q))
155.7605850060304

I am trying to reconcile SPY Bloomberg Terminal option data, but for some reason, it doesn't match. I would expect this to match the Mid for the 475 strike, but it doesn't

T = 30/365
q = 0
r = 0.0548
S0 = 474.93
payoff = 'call'
K = 475
F = 477.1
print(BlackScholesWithForwards(payoff='call', F=F, K=K, T=T, r=r, sigma=11.84289027/100, q=q))
8.771200411422967

Option monitor bloomberg data, as of 17 Jan 24:

Expiry Days to Expiry Contract Size Risk-Free Rate Forward Price
16-Feb-24 (30d) 30 100 5.480000 477.100000
Strike Ticker Bid Ask Last IVM Volm
470 SPY 2/16/24 C470 11.279999 11.329999 11.109999 12.769134 1322
471 SPY 2/16/24 C471 10.550000 10.600000 10.020000 12.529111 1048
472 SPY 2/16/24 C472 9.840000 9.880000 9.859999 12.406106 1355
473 SPY 2/16/24 C473 9.159999 9.189999 9.140000 12.176440 1285
474 SPY 2/16/24 C474 8.489999 8.520000 8.510000 12.000890 3941
475 SPY 2/16/24 C475 7.849999 7.880000 7.880000 11.842890 10970
476 SPY 2/16/24 C476 7.239999 7.260000 7.230000 11.700001 6087
477 SPY 2/16/24 C477 6.650000 6.670000 6.670000 11.542202 4000
import numpy as np
import scipy.stats as ss

def BlackScholesWithForwards(payoff='call', F=1000, K=100, T=None, r=None, sigma=0.35, q=0):
    # Check if T and r are provided; if not, use default values or raise an exception
    if T is None or r is None:
        raise ValueError("Please provide values for T and r")

    d1 = (np.log(F / K) + (r - q + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    
    if payoff == "call":
        return np.exp(-q * T) * (F * ss.norm.cdf(d1) - K * np.exp(-r * T) * ss.norm.cdf(d2))
    elif payoff == "put":
        return np.exp(-r * T) * (K * ss.norm.cdf(-d2) - F * np.exp(-q * T) * ss.norm.cdf(-d1))
    else:
        raise ValueError("Invalid value for payoff. Use 'call' or 'put'.")
$\endgroup$
2
  • 3
    $\begingroup$ You don't use continuous rates, and ignore that's it's American exercise. These are market quotes and you probably get the values from OMON? Where do you got the forward from (it's not quoted) and where the IV? Look at OVME L for listed mode for the ticker. The time of all market data must be exactly equal to when the last quote and IV was computed (on OMON). $\endgroup$
    – AKdemy
    Commented Jan 17 at 18:59
  • $\begingroup$ @AKdemy yes, I got my data, forward, IVM (IV for mid) all all from OMON. So I should look at OVME L instead? $\endgroup$
    – Skittles
    Commented Jan 17 at 19:09

1 Answer 1

5
$\begingroup$

You can use Bloomberg to get the bulk of work done for you:

  • If you load the ticker in OVME L, you load the OTC pricer OVME in listed mode. That will also load the market value of the option and all market data at the secific time it was loaded.
  • Within OVME, make sure your setting is set to use exact time to expiry (minutes, not just integer days). You will see the hours to expiry alongside the date.
  • In more market data (OVME has a few settings, so the exact look can differ), you can see all rates, the forward and dividends.
  • You can start by setting rates and divs to zero. That way its easiest to match
  • Once you are done with that look at rates (for calls, European and American will be identical). Black is for continuous rates.
  • Afterwards, look at dividends. If the ticker loads with dividends, you can no longer use the simple formula you used (which is in fact incorrect as well). If you see that Bloomberg uses discrete, it uses a PDE solver and discrete dividend payments to compute the American option value.
  • SPY options are American options. Therefore, a simple BS pricing tool will not be sufficient.

If you use the forward, you look at Black76. In this case, you no longer have dividends and interest rates in the closed form solution because these are incorporated into the forward price already.

In dummy code, you either use

 BSM(s,k,t,r,d, σ, cp)
        d1 = ( log(s/k) + (r - d+ σ^2/2)*t ) / (σ*sqrt(t))
        d2 = d1 - σ*sqrt(t) 
        opt = exp(-d*t)*cp*s*N(cp*d1) - cp*k*exp(-r*t)*N(cp*d2)

where cp is a put / call flag which is 1 for a call, and -1 for a put and r and d need to be transformed to the continuous analogue of the discrete rate from the market quote.

or

Black76(F,K,t,r,σ, cp)
    d1 = (log(F/K) + 0.5*σ^2*t)/ σ*sqrt(t)
    d2 = d1 - σ*sqrt(t)
    opt = cp*exp(-r*t)*(F*N(cp*d1) - K*N(cp*d2))

What you did was mixing these two formulas together.

EDIT

As mentioned in the comment, you need to look at the exact details of the settings. Interest rates are usually following its own market convention. Also, if you solve for IV you may not get to see the entire precision. To simplify things, enter values manually where you do see all precision (and therefore solve for price as opposed to IV). I do not know your settings, but I will include screenshots of mine, and all values used in pricing.

I priced the following option:

enter image description here

  • Interest Rate Settlement in the Market Data tab can be Market Convention or 0 Days. The former is T+2 (hence 2 days less than the option expiry).
  • Daycount Convention in the Pricing tab is Act/360 if set to Swap Convention.
  • I manually entered "clean" values so that there is no problem with decimal precision that is invisible in the GUI. I also set the day to be exactly a full day, to avoid further complication with hours and minutes to expiry.

Within Python, you can use the following

# packages 
import numpy as np
from scipy.stats import norm
import pandas as pd
# Black Scholes formula
def BSM(S,K,r,d,t, sigma, cp_flag):
    d1 = ((np.log(S/K) + (r - d + 0.5 * sigma **2) * t) / (sigma * np.sqrt (t)))
    d2 = d1 - sigma * np.sqrt(t) 
    opt = cp_flag*S *np.exp(-d*t)* norm.cdf(cp_flag*d1) - cp_flag* K * np.exp(-r*t)  * norm.cdf(cp_flag*d2)
    delta = cp_flag*np.exp(-d*t)*norm.cdf(cp_flag*d1)
    return opt, delta
# inputs 
s, k, t, σ, d  , r , cp_flag= 4860,  4860,  (90+ 0/24)/365, 0.1, 0.0 , 0.05, 1
r = np.log(1+r* (90-2)/360)/t # account for ACT/360 with T+2 and make it continuous
print(r)
#price option 
c = BSM(s,k,r,d, t, σ, cp_flag)
pd.set_option('float_format', '{:.5f}'.format)
pd.DataFrame({"Price (Share)" : [c[0]], 
                            "Delta": [c[1]*100]})

The output matches the call option price and delta from above exactly. enter image description here

Ultimately, pricing even the simplest vanilla options is quite complicated once you take into account all market conventions and details. That's why replicating options prices from proper pricing tools is difficult, and sometimes impossible with certain tools like quantlib because the functionality simply does not support all details.

Some examples:

$\endgroup$
3
  • $\begingroup$ would appreciate it if you could have a look at my edit, thanks. $\endgroup$
    – Skittles
    Commented Jan 19 at 7:41
  • 3
    $\begingroup$ I am too busy this week but will respond next week, or over the weekend. You need to look at your settings. By default, your interest rate will have a different day count (the one associated with the rate itself). Also, you don't see the whole precision in IV. Best to manually enter values without decimals to avoid any rounding errors. $\endgroup$
    – AKdemy
    Commented Jan 22 at 20:57
  • $\begingroup$ ok thanks, looked into OVME settings->Pricing->DayCount convention and others.. but still, I can't match the bbg calcs once I add the interest rate in OVME L (as cont). Neither USD cont rate * 365/360 helped to match the result. $\endgroup$
    – Skittles
    Commented Jan 24 at 8:01

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.