Implied Volatility Skew¶
Back out per-strike IV from live premiums via bisection.
Part 12 of 37 in the ServLoci algo/options trading notebook series — full index in notebooks/README.md.
Setup¶
# Get your dedicated static IPv6 + SOCKS5 credentials free:
# https://comm.servloci.in/register (or /auth/google?free=1 for an instant trial)
# Your api_key / api_secret pair shows up in the portal after signup:
# https://comm.servloci.in/user
!pip install -q "requests[socks]"
!curl -sL https://comm.servloci.in/sdk/servloci.py -o servloci.py
import os
from servloci import ServLoci
SERVLOCI_API_KEY = os.environ.get("SERVLOCI_API_KEY", "dhan:1000000001") # broker:client_id
SERVLOCI_API_SECRET = os.environ.get("SERVLOCI_API_SECRET", "") # from the portal — leave blank to run this notebook in demo mode
sl = None
if SERVLOCI_API_SECRET:
sl = ServLoci(api_key=SERVLOCI_API_KEY, api_secret=SERVLOCI_API_SECRET)
print("ServLoci configured:", sl.host, sl.port)
else:
print("SERVLOCI_API_SECRET not set — running in demo mode (no live proxy calls).")
What implied volatility actually is¶
Black-Scholes takes volatility as an input and produces a price. Implied volatility (IV) runs that backwards: given the price the market is actually paying for an option, what volatility assumption would make the model agree? There's no closed-form inverse for that, so it's solved numerically — bisection here, searching for the vol that reprices the option to match its observed premium.
IV is best read as the market's forecast of future realized volatility over the option's remaining life, expressed in price rather than in a probability. It is not a measurement of past price movement (that's realized/historical vol, notebook 13) — it's a forward-looking, and often wrong, consensus.
Why IV differs by strike (skew). If Black-Scholes were literally true, every strike on the same underlying and expiry would imply the same volatility. In practice they don't — equity index options typically show a downward-sloping "skew," where OTM puts carry higher IV than OTM calls. That's the market pricing in a fatter left tail: crashes happen faster than rallies, so demand for downside protection (buying puts) bids up their implied vol relative to the model's flat-vol assumption. Reading a skew chart: a steep negative slope going into OTM puts signals the market is currently paying up for crash insurance; a flat chain suggests complacency.
Backs out implied volatility per strike via bisection against the Black-Scholes pricer (notebook 06), using live chain LTPs (notebook 11) — a rough call-side skew, not a full surface (single expiry assumption below).
from scipy.stats import norm
import math
def bs_price(opt_type, spot, strike, t_years, vol, rate=0.065):
if t_years <= 0 or vol <= 0:
return max(spot - strike, 0) if opt_type == "CE" else max(strike - spot, 0)
d1 = (math.log(spot / strike) + (rate + vol * vol / 2) * t_years) / (vol * math.sqrt(t_years))
d2 = d1 - vol * math.sqrt(t_years)
if opt_type == "CE":
return spot * norm.cdf(d1) - strike * math.exp(-rate * t_years) * norm.cdf(d2)
return strike * math.exp(-rate * t_years) * norm.cdf(-d2) - spot * norm.cdf(-d1)
def implied_vol(opt_type, spot, strike, t_years, market_price, rate=0.065, lo=0.01, hi=3.0, iters=60):
for _ in range(iters):
mid = (lo + hi) / 2
if bs_price(opt_type, spot, strike, t_years, mid, rate) > market_price:
hi = mid
else:
lo = mid
return (lo + hi) / 2
import requests
resp = requests.get("https://comm.servloci.in/api/market/option-chain", params={"symbol": "NIFTY"}, timeout=10)
data = resp.json()
spot, chain = data.get("spot", 24000), data.get("strikes", [])
t_years = 7 / 365 # placeholder — replace with actual days-to-expiry / 365
strikes, ivs = [], []
for row in chain:
ltp = (row.get("ce") or {}).get("ltp")
if isinstance(ltp, (int, float)) and ltp > 0:
strikes.append(row["strike"])
ivs.append(implied_vol("CE", spot, row["strike"], t_years, ltp) * 100)
import matplotlib.pyplot as plt
plt.plot(strikes, ivs, marker="o")
plt.axvline(spot, color="grey", linestyle="--", label="spot")
plt.title("NIFTY call IV skew (single-expiry proxy)")
plt.xlabel("Strike")
plt.ylabel("Implied vol (%)")
plt.legend()
plt.show()
« Previous: Live Option Chain Fetch
Next: Historical Data for Backtesting »
Try the concepts above interactively: Options Strategy Builder · Docs · Get your static IP