{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7aa4ae69",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e643b553",
   "metadata": {},
   "source": [
    "This notebook calibrates the SABR (Stochastic Alpha, Beta, Rho) model to market data, specifically for the SPY options expiring in January 2026. It fetches option chains, extracts call and put options, and calculates the mid-prices. The SABR model is used to fit implied volatilities and generate a volatility smile. Finally, the code uses the Black model to calculate theoretical option prices and compares them to market prices. This is useful in practice for financial modeling and option pricing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e822978d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from openbb_terminal.sdk import openbb\n",
    "from pysabr import Hagan2002LognormalSABR\n",
    "from pysabr import hagan_2002_lognormal_sabr as sabr\n",
    "from pysabr.black import lognormal_call"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5030ab09",
   "metadata": {},
   "source": [
    "Set plot style and parameters for visualizations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17060c09",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.style.use(\"default\")\n",
    "plt.rcParams[\"figure.figsize\"] = [5.5, 4.0]\n",
    "plt.rcParams[\"figure.dpi\"] = 140\n",
    "plt.rcParams[\"lines.linewidth\"] = 0.75\n",
    "plt.rcParams[\"font.size\"] = 8"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0a074da",
   "metadata": {},
   "source": [
    "Define the symbol and expiration date for the SPY options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2d1fdb5",
   "metadata": {},
   "outputs": [],
   "source": [
    "symbol = \"SPY\"\n",
    "expiration = \"2026-01-16\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf1880a3",
   "metadata": {},
   "source": [
    "Fetch the option chains for the given symbol from Yahoo Finance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cccd411c",
   "metadata": {},
   "outputs": [],
   "source": [
    "spy = openbb.stocks.options.chains(symbol, source=\"YahooFinance\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c28a3ce1",
   "metadata": {},
   "source": [
    "Extract call options and calculate their mid-prices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de2ec85c",
   "metadata": {},
   "outputs": [],
   "source": [
    "calls = spy[spy.optionType == \"call\"]\n",
    "jan_2026_c = calls[calls.expiration == expiration].set_index(\"strike\")\n",
    "jan_2026_c[\"mid\"] = (jan_2026_c.ask + jan_2026_c.ask) / 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b365dea",
   "metadata": {},
   "source": [
    "Extract put options and calculate their mid-prices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "184af719",
   "metadata": {},
   "outputs": [],
   "source": [
    "puts = spy[spy.optionType == \"put\"]\n",
    "jan_2026_p = puts[puts.expiration == expiration].set_index(\"strike\")\n",
    "jan_2026_p[\"mid\"] = (jan_2026_p.ask + jan_2026_p.ask) / 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1cfcfad",
   "metadata": {},
   "source": [
    "Extract strikes and implied volatilities for the call options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d804f968",
   "metadata": {},
   "outputs": [],
   "source": [
    "strikes = jan_2026_c.index\n",
    "vols = jan_2026_c.impliedVolatility * 100"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b23b23f7",
   "metadata": {},
   "source": [
    "Find the forward price using put-call parity and calculate the time to expiration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0fdc6eeb",
   "metadata": {},
   "outputs": [],
   "source": [
    "f = (\n",
    "    (jan_2026_c.mid - jan_2026_p.mid)\n",
    "    .dropna()\n",
    "    .abs()\n",
    "    .sort_values()\n",
    "    .index[0]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e29bc873",
   "metadata": {},
   "source": [
    "Calculate the time fraction until expiration in years"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3fbcafa0",
   "metadata": {},
   "outputs": [],
   "source": [
    "t = (pd.Timestamp(expiration) - pd.Timestamp.now()).days / 365"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6c823e1",
   "metadata": {},
   "source": [
    "Set the beta parameter for the SABR model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3358162e",
   "metadata": {},
   "outputs": [],
   "source": [
    "beta = 0.5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0546bd3",
   "metadata": {},
   "source": [
    "Initialize the SABR model with the forward price, time to expiration, and beta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "618ffe6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "sabr_lognormal = Hagan2002LognormalSABR(\n",
    "    f=f,\n",
    "    t=t,\n",
    "    beta=beta\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cc847c8",
   "metadata": {},
   "source": [
    "Fit the SABR model to the market implied volatilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81e7bee9",
   "metadata": {},
   "outputs": [],
   "source": [
    "alpha, rho, volvol = sabr_lognormal.fit(strikes, vols)\n",
    "print(alpha, rho, volvol)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c744107",
   "metadata": {},
   "source": [
    "Calculate calibrated volatilities using the SABR model for each strike price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4bc7072b",
   "metadata": {},
   "outputs": [],
   "source": [
    "calibrated_vols = [\n",
    "    sabr.lognormal_vol(strike, f, t, alpha, beta, rho, volvol) * 100\n",
    "    for strike in strikes\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0df4deb9",
   "metadata": {},
   "source": [
    "Plot the volatility smile generated by the SABR model and compare it to market implied volatilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f74ef42",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(\n",
    "    strikes, \n",
    "    calibrated_vols\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27e406d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.xlabel(\"Strike\")\n",
    "plt.ylabel(\"Volatility\")\n",
    "plt.title(\"Volatility Smile\")\n",
    "plt.plot(strikes, vols)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b6b09053",
   "metadata": {},
   "source": [
    "Calculate theoretical option prices using the Black model for each strike price and calibrated volatility"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17ee92ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "black_values = []\n",
    "for strike, calibrated_vol in zip(strikes.tolist(), calibrated_vols):\n",
    "    black_value = lognormal_call(\n",
    "        strike, \n",
    "        f, \n",
    "        t, \n",
    "        calibrated_vol / 100, \n",
    "        0.05, \n",
    "        cp=\"call\"\n",
    "    )\n",
    "    black_values.append(black_value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07f10dc9",
   "metadata": {},
   "source": [
    "Create a DataFrame to compare Black model values with market mid-prices and plot the differences"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66d3bf47",
   "metadata": {},
   "outputs": [],
   "source": [
    "option_values = pd.DataFrame(\n",
    "    {\n",
    "        \"black\": black_values,\n",
    "        \"market\": jan_2026_c.mid\n",
    "    },\n",
    "    index=strikes\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3682905",
   "metadata": {},
   "outputs": [],
   "source": [
    "(option_values.black - option_values.market).plot.bar()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7da06c5",
   "metadata": {},
   "source": [
    "<a href=\"https://pyquantnews.com/\">PyQuant News</a> is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to <a href=\"https://gettingstartedwithpythonforquantfinance.com/\">get started with Python for quant finance</a>. For educational purposes. Not investment advise. Use at your own risk."
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
