Black-Scholes Option Pricing, Black-Scholes Model, Options Greeks explained with Python

đ§ Why Black-Scholes Option Pricing matters
If you trade options, you are not just trading direction.
You are trading:
- Probability
- Time
- Uncertainty
The Black-Scholes model converts all of this into:
Price + Greeks â actual P&LÂ behavior
đŻ What you will build
From your notebook:
- Real data using yfinance
- A contract â model â output pipeline
- Visual understanding of:
- Delta
- Gamma
- Theta
- And finally: P&L intuition
đ§ Step 1: Black-Scholes Engine
import numpy as np
from scipy.stats import norm
def black_scholes_call(S, K, T, r, sigma):
d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
delta = norm.cdf(d1)
gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
theta = (-S * norm.pdf(d1) * sigma / (2 * np.sqrt(T))
- r * K * np.exp(-r * T) * norm.cdf(d2)) / 365
vega = (S * norm.pdf(d1) * np.sqrt(T)) / 100
return {
"price": call_price,
"delta": delta,
"gamma": gamma,
"theta": theta,
"vega": vega
}
Step 2: Get Market Data
import yfinance as yf
ticker = yf.Ticker("AAPL")
spot = ticker.history(period="1d")["Close"].iloc[-1]
expiry = ticker.options[0]
calls = ticker.option_chain(expiry).calls

đ This gives you the real option chain
đ§ Step 3: Parse Contracts
def parse_contract_symbol(symbol):
underlying = symbol[:-15]
expiry_str = symbol[-15:-9]
option_type = symbol[-9]
strike_str = symbol[-8:]
expiry = pd.to_datetime(expiry_str, format="%y%m%d")
strike = int(strike_str) / 1000
return pd.Series([underlying, expiry, option_type, strike])
Step 4: Build Selection Layer (df1)
Contracts = calls[[
'underlying',
'parsed_strike',
'expiry',
'option_type',
'contract_size',
'lastPrice',
'impliedVolatility',
'inTheMoney'
]].rename(columns={
'parsed_strike': 'strike',
'lastPrice': 'market_price'
})
đ This is your decision layer (what to trade)
đ§ Step 5: Build Model Inputs (df2)
def build_bs_inputs(row, spot, r=0.05):
delta = row['expiry'] - datetime.now()
T = delta.total_seconds() / (365 * 24 * 60 * 60)
if T <= 0:
T = 1 / 365
sigma = row['impliedVolatility']
if pd.isna(sigma) or sigma < 0.01:
sigma = 0.2
return pd.DataFrame([{
"Spot Price": spot,
"Strike Price": row['strike'],
"Time to Expiry": T,
"Volatility": sigma,
"Interest Rate": r
}])

Step 6: Run Model
res = black_scholes_call(
S=df2["Spot Price"].iloc[0],
K=df2["Strike Price"].iloc[0],
T=df2["Time to Expiry"].iloc[0],
r=df2["Interest Rate"].iloc[0],
sigma=df2["Volatility"].iloc[0]
)
đ Output Interpretation
From your notebook:
Price â 23.86
Delta = 1.00
Gamma â 0
Theta â -0.032
Vega â 0
đ§ Meaning
- Delta = 1 â behaves like stock
- Gamma â 0 â no convexity
- Vega â 0 â no uncertainty
- Theta small â mostly intrinsic
đ This is a deep ITMÂ option
đ Step 7: Delta vs Stock Price
S_range = np.linspace(200, 300, 100)
deltas = [
black_scholes_call(
S,
df2["Strike Price"].iloc[0],
df2["Time to Expiry"].iloc[0],
df2["Interest Rate"].iloc[0],
df2["Volatility"].iloc[0]
)['delta']
for S in S_range
]
plt.plot(S_range, deltas)

Interpretation
- Delta moves 0 â 1
- ATM = steep region
- ITM/OTM =Â flat
đ Transition from lottery â convex â stock
đ Step 8: Gamma vs Stock Price
gammas = [
black_scholes_call(
S,
df2["Strike Price"].iloc[0],
df2["Time to Expiry"].iloc[0],
df2["Interest Rate"].iloc[0],
df2["Volatility"].iloc[0]
)['gamma']
for S in S_range
]

Interpretation
- Peak at ATM
- Low elsewhere
đ Maximum convexity happens at the strike
đ Step 9: Theta vs Time
T_range = np.linspace(0.001, 1, 100)
thetas = [
black_scholes_call(
df2["Spot Price"].iloc[0],
df2["Strike Price"].iloc[0],
T,
df2["Interest Rate"].iloc[0],
df2["Volatility"].iloc[0]
)['theta']
for T in T_range
]
plt.plot(T_range, thetas)

Interpretation
- Theta becomes more negative near expiry
- Decay accelerates
- Longer time = slower decay
đ Time is not linear
Step 10: Theta vs Time (ATM vs ITM vs OTM)
đŠ ATM (Blue)
- Massive decay near expiry
- Highest uncertainty collapse
- đ Most dangerous zone
đ§ ITMÂ (Orange)
- Stable decay
- Mostly intrinsic
- đ Stock-like behavior
đ© OTMÂ (Green)
- Small decay early
- Sudden drop near expiry
- đ Lottery behavior
đ Step 11: Theta + Gamma Overlay (MOST IMPORTANT)

đŠ ATM (Battlefield)
- Gamma spikes
- Theta crashes
đ Move fast â big gain
đ No move â fast loss
đ§ ITMÂ (Stable)
- Low gamma
- Mild theta
đ Linear exposure
đ© OTM (Lottery)
- Low gamma
- Late theta
đ Nothing â then collapse
đ„ THE BIGÂ INSIGHT
Gamma (reward) and Theta (cost) peak together
đŻ What charts prove
- ATM = high risk, high reward
- ITM = stable, low convexity
- OTM = low probability, asymmetric
â ïž Critical trading insight
The most attractive trades (high gamma)
are also the ones bleeding the fastest (high theta)
đ§ Final Mental Model
Options = Probability Ă Time Ă Volatility
One-line takeaway
You are always paying Theta to own Gamma
Black-Scholes Option Pricing Explained: Black-Scholes Model with Code, Charts & Intuition was originally published in DataDrivenInvestor on Medium, where people are continuing the conversation by highlighting and responding to this story.