{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d75c1762",
   "metadata": {},
   "source": [
    "<div style=\"background-color:#000;\"><img src=\"pqn.png\"></img></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46b15169",
   "metadata": {},
   "source": [
    "This code retrieves option chains for a given ticker symbol using Yahoo Finance, then processes and visualizes the data. It fetches call and put options, calculates days to expiration, and filters based on implied volatility. The code further segments the options data by expiration date and strike price to plot implied volatility skew and term structure. Finally, it creates a 3D surface plot to visualize implied volatility across different strikes and expirations. This is useful for options analysis and trading strategies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b020a9f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import yfinance as yf\n",
    "import datetime as dt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23169cbf",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "582752e3",
   "metadata": {},
   "source": [
    "Define a function to retrieve and process option chains for a given ticker symbol"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7883ca7d",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "def option_chains(ticker):\n",
    "    \"\"\"Retrieve and process option chains.\n",
    "    \n",
    "    This function fetches option chains from Yahoo Finance for a given ticker symbol, \n",
    "    processes the data, and calculates days to expiration for each option.\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    ticker : str\n",
    "        The ticker symbol of the asset.\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    chains : pd.DataFrame\n",
    "        DataFrame containing processed option chains with days to expiration.\n",
    "    \"\"\"\n",
    "    \n",
    "    # Fetch option chains from Yahoo Finance for the given ticker\n",
    "    asset = yf.Ticker(ticker)\n",
    "    expirations = asset.options\n",
    "    \n",
    "    chains = pd.DataFrame()\n",
    "    \n",
    "    for expiration in expirations:\n",
    "        # Retrieve option chain for a specific expiration date\n",
    "        opt = asset.option_chain(expiration)\n",
    "        \n",
    "        calls = opt.calls\n",
    "        calls['optionType'] = \"call\"\n",
    "        \n",
    "        puts = opt.puts\n",
    "        puts['optionType'] = \"put\"\n",
    "        \n",
    "        # Concatenate call and put options into a single DataFrame\n",
    "        chain = pd.concat([calls, puts])\n",
    "        chain['expiration'] = pd.to_datetime(expiration) + pd.DateOffset(hours=23, minutes=59, seconds=59)\n",
    "        \n",
    "        chains = pd.concat([chains, chain])\n",
    "    \n",
    "    # Calculate days to expiration for each option\n",
    "    chains[\"daysToExpiration\"] = (chains.expiration - dt.datetime.today()).dt.days + 1\n",
    "    \n",
    "    return chains"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7548148b",
   "metadata": {},
   "source": [
    "Retrieve the option chains for the SPY ticker symbol"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2fd4f869",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = option_chains(\"SPY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6f20ede",
   "metadata": {},
   "source": [
    "Filter the option chains to get only call options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3b39a055",
   "metadata": {},
   "outputs": [],
   "source": [
    "calls = options[options[\"optionType\"] == \"call\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21838163",
   "metadata": {},
   "source": [
    "Select call options that expire on 2023-01-20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e289397",
   "metadata": {},
   "outputs": [],
   "source": [
    "calls_at_expiry = calls[calls[\"expiration\"] == \"2023-01-20 23:59:59\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef86ea45",
   "metadata": {},
   "source": [
    "Filter out call options with low implied volatility"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4fb5275",
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_calls_at_expiry = calls_at_expiry[calls_at_expiry.impliedVolatility >= 0.001]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cdc711d",
   "metadata": {},
   "source": [
    "Plot implied volatility skew for call options expiring on 2023-01-20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8a97578",
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_calls_at_expiry[[\"strike\", \"impliedVolatility\"]].set_index(\"strike\").plot(\n",
    "    title=\"Implied Volatility Skew\", figsize=(7, 4)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96a47554",
   "metadata": {},
   "source": [
    "Select options with a strike price of 400.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc10d700",
   "metadata": {},
   "outputs": [],
   "source": [
    "calls_at_strike = options[options[\"strike\"] == 400.0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7f5080e",
   "metadata": {},
   "source": [
    "Filter out options with low implied volatility"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "055393a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_calls_at_strike = calls_at_strike[calls_at_strike.impliedVolatility >= 0.001]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d06e635d",
   "metadata": {},
   "source": [
    "Plot implied volatility term structure for options with a strike price of 400.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85184a63",
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_calls_at_strike[[\"expiration\", \"impliedVolatility\"]].set_index(\"expiration\").plot(\n",
    "    title=\"Implied Volatility Term Structure\", figsize=(7, 4)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "439c1746",
   "metadata": {},
   "source": [
    "Pivot the DataFrame to prepare data for a 3D surface plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f05c6eca",
   "metadata": {},
   "outputs": [],
   "source": [
    "surface = (\n",
    "    calls[['daysToExpiration', 'strike', 'impliedVolatility']]\n",
    "    .pivot_table(values='impliedVolatility', index='strike', columns='daysToExpiration')\n",
    "    .dropna()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35a8ce2a",
   "metadata": {},
   "source": [
    "Create a figure object for the 3D plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aaece560",
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(10, 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e817706c",
   "metadata": {},
   "source": [
    "Add a 3D subplot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c859d74c",
   "metadata": {},
   "outputs": [],
   "source": [
    "ax = fig.add_subplot(111, projection='3d')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51cff93c",
   "metadata": {},
   "source": [
    "Prepare coordinate matrices for the 3D plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a3cf2cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y, z = surface.columns.values, surface.index.values, surface.values\n",
    "X, Y = np.meshgrid(x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3983db1",
   "metadata": {},
   "source": [
    "Set axis labels and title"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d2be8ca5",
   "metadata": {},
   "outputs": [],
   "source": [
    "ax.set_xlabel('Days to expiration')\n",
    "ax.set_ylabel('Strike price')\n",
    "ax.set_zlabel('Implied volatility')\n",
    "ax.set_title('Call implied volatility surface')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "774bbcb1",
   "metadata": {},
   "source": [
    "Plot the 3D surface of implied volatility"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d281abb",
   "metadata": {},
   "outputs": [],
   "source": [
    "ax.plot_surface(X, Y, z)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a65a16aa",
   "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
}
