Strasmore Research
Learn Matt ConnorBy Matt Connor · Updated 2026-08-05

How Implied Volatility Is Calculated

Implied volatility has no closed form solution. See how a solver backs it out of an option price by iteration, the Python that does it, and the traps.

Implied volatility is calculated by iteration, not by formula. No closed form expression turns an option's market price back into a volatility number, so a solver guesses a volatility, prices the option with Black-Scholes, compares that model price against the quote, and repeats until the two agree to the penny. What follows is the whole method: the pricing function, the search loop in standard library Python, and the reasons two vendors publish different numbers for the same contract.

Why implied volatility has no closed form formula

Black-Scholes runs one direction. Hand it a spot price, a strike, a time to expiry, an interest rate and a volatility, and it returns a theoretical price. Five of those six quantities are observable. Volatility is not. It is an assumption about how far the stock will travel between now and expiry.

Traders invert the problem. The price is on the screen, and volatility is the unknown. Implied volatility is the volatility input that makes the Black-Scholes price equal the option's market price. Sigma, the volatility term, sits inside the normal distribution function twice, in both d1 and d2, and no rearrangement isolates it. Algebra stops there. Numerical search takes over. What implied volatility measures covers the interpretation; this page covers the machinery.

Two properties of the model make the search easy. A call's model price rises whenever volatility rises, with no exceptions, and it moves smoothly. A quantity that only climbs can be cornered by squeezing a bracket around it.

The solver runs once per contract

The output is one number per contract, not one per stock, and contracts on the same stock in the same expiry window disagree with each other. Every Apple (AAPL) option with 20 to 45 days left that traded on June 30, 2026, grouped by strike as a fraction of the share price:

QueryOne solve per contract: AAPL implied volatility by strike zone, June 30, 2026
The exact SQL behind every number
WITH toFloat64(strike_price) / toFloat64(underlying_close) AS moneyness
SELECT multiIf(moneyness < 0.90, '0.80-0.90',
               moneyness < 0.95, '0.90-0.95',
               moneyness < 1.00, '0.95-1.00',
               moneyness < 1.05, '1.00-1.05',
               moneyness < 1.10, '1.05-1.10',
                                 '1.10-1.20') AS strike_vs_spot,
       round(100 * avg(toFloat64(implied_volatility)), 1) AS implied_vol_pct,
       count() AS contract_count
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
  AND date = toDate('2026-06-30')
  AND days_to_expiry BETWEEN 20 AND 45
  AND iv_converged = 1
  AND volume > 0
  AND moneyness BETWEEN 0.80 AND 1.20
GROUP BY strike_vs_spot
ORDER BY min(moneyness)
Run this yourself

Reading up the strike ladder, the 0.80-0.90 zone solved to 35.5%, the 0.95-1.00 zone to 28.1%, and the 1.10-1.20 zone to 27.3%. One company, one session, 6 answers. The bend in that curve has a name, the volatility skew, and a single volatility per stock cannot draw it.

The Black-Scholes price the solver has to match

S is the share price, K the strike, T the time to expiry in years, r the risk free rate, and N() the standard normal cumulative distribution, meaning the probability that a standard normal draw lands below a given point. Python carries that last piece in math.erf, so the interpreter alone is enough.

apt-get update && apt-get install -y python3

Prefix both commands with sudo on a machine where you are not root. Then save this as iv.py:

import math

def norm_cdf(x):
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

def bs_call(S, K, T, r, sigma):
    if T <= 0.0 or sigma <= 0.0:
        return max(S - K, 0.0)
    d1 = (math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
    d2 = d1 - sigma * math.sqrt(T)
    return S * norm_cdf(d1) - K * math.exp(-r * T) * norm_cdf(d2)

That is the forward model in full. Feed it a volatility, get a price.

How implied volatility is calculated, step by step

Bisection is the method to learn first. It cannot diverge, and it needs no calculus.

  1. Bracket the answer between 0.01 (1% a year) and 5.0 (500%). Every traded option sits inside that range.
  2. Price the option at the midpoint of the bracket.
  3. If the model price sits above the market quote, the guess was too high: move the top of the bracket down to the midpoint. If it sits below, move the bottom up.
  4. Stop once the model price lands within a cent of the quote.
def implied_vol(price, S, K, T, r, lo=0.01, hi=5.0, tol=0.01):
    for _ in range(100):
        mid = 0.5 * (lo + hi)
        diff = bs_call(S, K, T, r, mid) - price
        if abs(diff) < tol:
            return mid
        if diff > 0.0:
            hi = mid
        else:
            lo = mid
    return 0.5 * (lo + hi)

# $100 stock, $100 strike, three months, 4% rates, $5.00 on the screen
print(round(implied_vol(5.00, 100.0, 100.0, 0.25, 0.04), 4))

Run python3 iv.py. It prints about 0.226, an implied volatility near 22.6% a year for that hypothetical quote. Each pass halves the bracket, and a range 4.99 wide halved twenty times is narrower than 0.00001, so the 100 iteration ceiling never binds.

Why Newton-Raphson converges faster, and where it breaks

Bisection throws away information the model already holds. Vega is the option's price change per unit of volatility, and Black-Scholes gives it in closed form. Newton-Raphson treats it as a slope: measure the pricing error, divide by vega, step the guess by that much.

def bs_vega(S, K, T, r, sigma):
    d1 = (math.log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
    return S * math.sqrt(T) * math.exp(-0.5 * d1 * d1) / math.sqrt(2.0 * math.pi)

def implied_vol_newton(price, S, K, T, r, sigma=0.5):
    for _ in range(20):
        v = bs_vega(S, K, T, r, sigma)
        if v < 1e-8:
            return None          # no slope left, hand the job back to bisection
        step = (bs_call(S, K, T, r, sigma) - price) / v
        sigma -= step
        if sigma <= 0.0:
            return None          # the step overshot into nonsense
        if abs(step) < 1e-6:
            return sigma
    return None

Near the money that lands in three or four passes against bisection's dozen. The failure mode sits in the denominator: vega shrinks as a strike moves away from the share price. The same AAPL contracts, with each zone's average vega shown as a percentage of the at the money reading:

QueryWhere the Newton step misbehaves: AAPL vega by strike zone, June 30, 2026
The exact SQL behind every number
WITH toFloat64(strike_price) / toFloat64(underlying_close) AS moneyness,
     (
         SELECT avg(toFloat64(vega))
         FROM global_markets.options_greeks
         WHERE underlying_symbol = 'AAPL'
           AND date = toDate('2026-06-30')
           AND days_to_expiry BETWEEN 20 AND 45
           AND iv_converged = 1
           AND volume > 0
           AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.025
     ) AS atm_vega
SELECT multiIf(moneyness < 0.90, '0.80-0.90',
               moneyness < 0.95, '0.90-0.95',
               moneyness < 1.00, '0.95-1.00',
               moneyness < 1.05, '1.00-1.05',
               moneyness < 1.10, '1.05-1.10',
                                 '1.10-1.20') AS strike_vs_spot,
       round(100 * avg(toFloat64(vega)) / atm_vega, 1) AS vega_pct_of_atm,
       count() AS contract_count
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
  AND date = toDate('2026-06-30')
  AND days_to_expiry BETWEEN 20 AND 45
  AND iv_converged = 1
  AND volume > 0
  AND moneyness BETWEEN 0.80 AND 1.20
GROUP BY strike_vs_spot
ORDER BY min(moneyness)
Run this yourself

The wings carry a fraction of the sensitivity: 33.1% of the at the money vega in the 0.80-0.90 zone, 31.6% in the 1.10-1.20 zone. Dividing a pricing error by a number that small produces an enormous step, which can throw the guess below zero, where the model has nothing left to say. Production solvers pair the two methods, bracketing first and polishing with Newton. Vega covers the greek itself.

Why two sources report different implied volatility

The model is public and the arithmetic is settled, so the disagreement lives in the inputs.

  • Mid versus last. The solver needs one price. A contract quoted $2.00 bid and $2.20 offered has a $2.10 midpoint, while the last print might be $2.02 from ninety minutes ago. On a contract like that, ten cents of price is worth more than a point of volatility.
  • Dividends and carry. The function above prices a European call on a stock paying nothing. A dividend before expiry lowers the forward price, and each desk applies its own adjustment and its own rate.
  • American early exercise. US single stock options can be exercised any day, and that right holds value the European formula has nowhere to put. A solver that ignores it pushes the difference into volatility. Binomial trees price the exercise right directly.
  • Stale quotes. A strike that last traded on Tuesday still shows a quote, and the solver treats whatever it is handed as the truth.

Convergence makes the last two visible. The panel below takes eight household names on June 30, 2026, counts traded contracts with 20 to 45 days left, and reports both the at the money volatility and the share of contracts where the solve converged:

QueryAt the money implied volatility and solver convergence, eight names, June 30, 2026
The exact SQL behind every number
WITH abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) AS distance_from_spot
SELECT underlying_symbol AS symbol,
       round(100 * avgIf(toFloat64(implied_volatility), iv_converged = 1 AND distance_from_spot < 0.05), 1) AS atm_iv_pct,
       round(100 * countIf(iv_converged = 1) / count(), 1) AS solved_pct,
       count() AS contract_count
FROM global_markets.options_greeks
WHERE date = toDate('2026-06-30')
  AND underlying_symbol IN ('AAPL', 'MSFT', 'NVDA', 'AMZN', 'TSLA', 'SPY', 'KO', 'JNJ')
  AND days_to_expiry BETWEEN 20 AND 45
  AND volume > 0
GROUP BY symbol
HAVING countIf(iv_converged = 1 AND distance_from_spot < 0.05) > 0
ORDER BY atm_iv_pct DESC
Run this yourself

TSLA carried the highest at the money reading of the 8 names at 47.7%, against 14.4% for SPY at the bottom. On the TSLA chain, 97.8% of traded contracts produced a converged answer. The remainder are strikes where the price handed to the solver sits outside what any volatility can reproduce, a quote under intrinsic value or a crossed market. Whether a number like that counts as high is a separate question: is 30% IV high takes it up, and the expected move converts it into a dollar range.

The same contract, re-solved every session

Nothing about implied volatility is stored. It is recomputed from whatever price is on the screen. Below is the most heavily traded AAPL contract expiring July 17, 2026, followed from June 1 into expiry. The strike never moved and the expiry never moved:

QueryOne AAPL contract, re-solved every session into its July 17, 2026 expiry
The exact SQL behind every number
WITH (
         SELECT ticker
         FROM global_markets.options_greeks
         WHERE underlying_symbol = 'AAPL'
           AND date = toDate('2026-06-30')
           AND expiration_date = toDate('2026-07-17')
           AND iv_converged = 1
           AND volume > 0
         ORDER BY volume DESC
         LIMIT 1
     ) AS pinned_contract
SELECT date,
       round(100 * avg(toFloat64(implied_volatility)), 1) AS implied_vol_pct,
       round(avg(days_to_expiry)) AS days_to_expiry
FROM global_markets.options_greeks
WHERE ticker = pinned_contract
  AND date BETWEEN toDate('2026-06-01') AND toDate('2026-07-17')
  AND iv_converged = 1
  AND volume > 0
GROUP BY date
ORDER BY date
Run this yourself

Across 29 sessions the solved reading opened at 25.1% with 46 days to run and finished at 26.6% with 4 days left. Every point on that line is one run of the loop above against that session's quote. Our per ticker pages publish the same solve: AAPL implied volatility runs it daily across the whole chain, so a reader can reproduce any figure there with the code on this page.

Implied volatility calculation FAQ

Is there a formula for implied volatility?

No. Black-Scholes maps volatility to price, and that mapping has no elementary inverse, since sigma appears inside the normal distribution twice. Every implied volatility number you meet, here or anywhere, comes out of an iterative solver.

How many iterations does the calculation take?

Each bisection pass halves the bracket, so a 0.01 to 5.0 range shrinks below 0.00001 in twenty passes, and matching a quote to the nearest cent usually takes about a dozen. Newton-Raphson gets there in three or four near the money, at the cost of steps that misbehave where vega is small.

Why do my broker and a data vendor show different implied volatility?

They fed the solver different inputs. The common gaps are mid quote versus last trade, the rate and dividend assumption, and whether the model handles American early exercise at all. On a thin strike, a stale quote alone can move the answer by several points.

Do calls and puts at the same strike give the same implied volatility?

In theory yes, put-call parity ties them together at a shared strike and expiry. In practice the two quotes are set independently, and the solved numbers differ a little, which is one more reason chain-wide averages vary between vendors.

Can I calculate implied volatility without market data?

Yes, for a hypothetical quote. The code above takes a price, a strike, a spot, a time to expiry and a rate, all typed by hand. Matching a live vendor number is the harder half: that takes the same inputs the vendor used.


Every panel here stores the SQL behind it. Open one, then run the same solve against a live chain on the Strasmore terminal.

#implied volatility#options#black-scholes#python#how-to