Skip to content
All library documents

Matching Bloomberg Option Prices Requires Correct Models and Conventions

Article Quant Q&A · Author: Skittles

Summary

The document explains why a standard Black-Scholes calculation may fail to reproduce Bloomberg prices for listed options. It distinguishes spot-based Black-Scholes pricing from Black-76 pricing on a forward: using a forward while also applying rates and dividends as if pricing from spot mixes conventions. It also points out that SPY options are American, and that discrete dividends can require a numerical pricing method rather than a simple closed-form formula.

The response recommends checking Bloomberg's exact time to expiry, forward, dividends, rate inputs, settlement timing, and day-count convention. Its examples show that apparently minor details, including hours to expiration and converting quoted rates into the model's continuous-rate convention, affect the result. A later answer discusses SPX settlement and trading cutoffs as further timing details. The examples illustrate reconciliation steps, but the matching result depends on the specific security, date, and terminal settings; there is no single convention that applies to every option.

Key ideas

  • Black-Scholes prices from spot and Black-76 prices from forwards use different input conventions.
  • A forward-based formula already incorporates carry, so adding spot-style rate and dividend adjustments can mix models.
  • American exercise and discrete dividends can require pricing methods beyond a simple Black-Scholes formula.
  • Exact expiry time, settlement rules, day count, and rate conventions can materially affect calculated prices.
  • Bloomberg inputs and settings must be matched precisely before comparing a model price with a quoted market price.

Tags

Full text
# Bloomberg terminal option data calculation


# Bloomberg terminal option data calculation












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?

```
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'.")
```

## Answer by AKdemy (score 7, accepted)

https://quant.stackexchange.com/a/78030

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:

- `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.

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:

- Replicating MATLAB's 30/360 (SIA) in Black-76

- Quantlib vs Bloomberg FX option valuation

- Price and Gamma of TYQ1C 133.00 COMB Comdty OVME where quantlib does not support the price quotation used by the exchange.

## Answer by Dorian B. (score 1)

https://quant.stackexchange.com/a/85317

The OP mentioned in a comment he was still not able to match the calculation. So here is another explanation/answer.

The contract expiration is on a Friday 19 April 2024 5pm. However, the SPX EOM options settles on the morning at 9:30AM based on SOQ. That is 7h and 30m earlier. Also trading stops 1 day before. So, `DTE=92-7.5/24=91.6875`

Calculating N(d1) gives exactly 59.04% delta. This only requires the `DTE` parameter and not the interest rate as we're using the forward directly.

```
import numpy as np
from scipy.stats import norm

F=4800.74
K=4740.00
iv=12.954/100
t=92-7.5/24
delta_pc=100*norm.cdf(np.log(F/K)/iv/np.sqrt(t/365)+0.5*iv*np.sqrt(t/365))
```

```
delta_pc
np.float64(59.04022811836342)
```

Moreover, the interest rate to be used is `5.135%`. You need to transform from ACT/360 continuous to ACT/365 contiguous and T-1 trading stop but T+1 clearing. In Python that would be:

```
r=np.log(1+(np.exp(0.05277*(t-1)/360)-1)*365/(t+1))
```

```
r
np.float64(0.05135632116705229)
```

This can be also easily verified by comparing the forward to the underlying which gives the same interest rate of `5.135%` at 92.6875 days (up to rounding, ie matching first 5 digits).

```
S=4739.21
r_carry=np.log(F/S)/t*365
```

```
r_carry=np.float64(0.051352260723443746)
```

Considering the fact that the delta uniquely prices an option, and the fact that the problem is over-determined as demonstrated above, I can only reasonably conclude the `154.700` is coming from somewhere else.

Net, the calculated price of the option should be `154.272`. The difference is around 19 days+ of interest and is too big to be some rounding error from any of the inputs.

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.