From 59417e56bcfc3c155a1e38846cb902142655b03f Mon Sep 17 00:00:00 2001 From: Attilio Pittelli <70145737+leo-ai-for-trading@users.noreply.github.com> Date: Sat, 27 May 2023 18:36:14 +0200 Subject: [PATCH] Fixed: Issue #47 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Replying to this issue Related Issue How to extract Implied volatility from Quantconnect #47 Types of changes -[ ] Bug fix (non-breaking change which fixes an issue) -[ ] Refactor (non-breaking change which improves implementation) -[ ] New feature (non-breaking change which adds functionality) -[ x] Non-functional change (xml comments/documentation/etc) Checklist: -[ x] My code follows the code style of this project. -[ x] I have read the CONTRIBUTING [document](https://github.com/QuantConnect/Lean/blob/master/CONTRIBUTING.md). -[ x] My branch follows the naming convention bug-- or feature-- --- Local_Stochastic_Volatility_Model.ipynb | 266 ++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 Local_Stochastic_Volatility_Model.ipynb diff --git a/Local_Stochastic_Volatility_Model.ipynb b/Local_Stochastic_Volatility_Model.ipynb new file mode 100644 index 0000000..bf621f9 --- /dev/null +++ b/Local_Stochastic_Volatility_Model.ipynb @@ -0,0 +1,266 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\n", + "
" + ], + "metadata": { + "id": "6iZIAgxW8Ari" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Dupire model\n", + "$C\\left(S_0, K, T\\right)=\\int_K^{\\infty} d S_T \\varphi\\left(S_T, T ; S_0\\right)\\left(S_T-K\\right)$\n", + "### risk-neutral density function φ of the final spot $S_T$\n", + "$\\varphi\\left(K, T ; S_0\\right)=\\frac{\\partial^2 C}{\\partial K^2}$\n", + "## Drift\n", + "$\\mu = r_t − D_t$\n", + "## local volatility\n", + "$\\sigma^2\\left(K, T, S_0\\right)=\\frac{\\frac{\\partial C}{\\partial T}}{\\frac{1}{2} K^2 \\frac{\\partial^2 C}{\\partial K^2}}$\n", + "## undiscounted option price C in terms of the strike price K:\n", + "### which is the Dupire equation when the underlying stock has risk-neutral drift μ.\n", + "$\\frac{\\partial C}{\\partial T}=\\frac{\\sigma^2 K^2}{2} \\frac{\\partial^2 C}{\\partial K^2}+\\left(r_t-D_t\\right)\\left(C-K \\frac{\\partial C}{\\partial K}\\right)$\n", + "# BSM model\n", + "$Call=S N\\left(d_1\\right)-K e^{-r \\tau} N\\left(d_2\\right)$ \n", + "\\\n", + "$d_1=\\frac{\\ln (S / K)+\\left(r-y+\\sigma^2 / 2\\right) \\tau}{\\sigma \\sqrt{\\tau}}$\n", + "\\\n", + "$d_2=\\frac{\\ln (S / K)+\\left(r-y-\\sigma^2 / 2\\right) \\tau}{\\sigma \\sqrt{\\tau}}=d_1-\\sigma \\sqrt{\\tau}$\n", + "\n", + "## PDF of Normal \n", + "$f(x)=\\frac{1}{\\sigma \\sqrt{2 \\pi}} e^{-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2}$\n", + "\n", + "## CDF of Normal\n", + "$N(x)=\\frac{1}{\\sqrt{2 \\pi}} \\int_{-\\infty}^x e^{-z^2 / 2} d z$\n" + ], + "metadata": { + "id": "fqocn6D58JDW" + } + }, + { + "cell_type": "markdown", + "source": [ + "# Importing Libraries" + ], + "metadata": { + "id": "LeIimwLE8FOu" + } + }, + { + "cell_type": "code", + "source": [ + "import sympy as smp \n", + "import numpy as np\n", + "from sympy.stats import P, E, variance, Die, Normal\n", + "from sympy import simplify\n" + ], + "metadata": { + "id": "dk37ioq7wpBX" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "# Risk-neutral density function φ of the final spot $S_T$\n", + "$\\varphi\\left(K, T ; S_0\\right)=\\frac{\\partial^2 C}{\\partial K^2}$" + ], + "metadata": { + "id": "BOs4Ha4_9Go_" + } + }, + { + "cell_type": "code", + "source": [ + "#define the function symbols\n", + "Call,varphi,N = smp.symbols('Call varphi N', cls=smp.Function) \n", + "S_0,K,T,S_T,D,r,sigma,tau ,d_1,d_2 = smp.symbols('S_0 K T S_T D r sigma tau d_1 d_2',real=True) \n", + "\n", + "\n", + "Call = smp.symbols('Call', cls=smp.Function) \n", + "f_d_1,f_d_2,F_d_1,F_d_2 = smp.symbols('f_d_1 f_d_2 F_d_1 F_d_2' , cls=smp.Function) \n", + "mu_google,sigma_google= smp.symbols('mu_google sigma_google',real=True) \n", + "f_d_1 = Normal('d_1', mu_google, sigma_google)\n", + "f_d_2 = Normal('d_2', mu_google, sigma_google)\n", + "\n", + "\n", + "F_d_1 = simplify(P(f_d_1>tau))\n", + "F_d_2 = simplify(P(f_d_2>tau))\n", + "Call = S_T * F_d_1 - K * smp.exp(-r*tau) * F_d_2\n", + "\n", + "\n", + "varphi = smp.diff(Call,K,2)\n", + "\n", + "#varphi.subs([(Call,Call),(K,int(K))]).evalf()" + ], + "metadata": { + "id": "0G1VQsJF8M0M" + }, + "execution_count": 4, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "\n", + "## Undiscounted option price C in terms of the strike price K:\n", + "### which is the Dupire equation when the underlying stock has risk-neutral drift μ.\n", + "$\\frac{\\partial C}{\\partial T}=\\frac{\\sigma^2 K^2}{2} \\frac{\\partial^2 C}{\\partial K^2}+\\left(r_t-D_t\\right)\\left(C-K \\frac{\\partial C}{\\partial K}\\right)$" + ], + "metadata": { + "id": "ORnbFO8A8XAJ" + } + }, + { + "cell_type": "code", + "source": [ + "dCdT = sigma**2 * K / 2 * varphi + (r - D)* (Call - K * smp.diff(Call,K))\n", + "dCdT" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 137 + }, + "id": "Xf5AbHNS8XT6", + "outputId": "fec23eba-66b6-4c57-8588-38d7d839f9ff" + }, + "execution_count": 5, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "S_T*(-D + r)*Piecewise((erf(sqrt(2)*(mu_google - tau)/(2*sigma_google))/2 + 1/2, (Abs(arg(sigma_google)) < pi/4) | ((Abs(arg(sigma_google)) <= pi/4) & (Abs(2*arg(mu_google) - 4*arg(sigma_google) + 2*arg((mu_google - tau)/mu_google) + 2*pi) < pi))), (sqrt(2)*Integral(exp(-(_z - mu_google + tau)**2/(2*sigma_google**2)), (_z, 0, oo))/(2*sqrt(pi)*sigma_google), True))" + ], + "text/latex": "$\\displaystyle S_{T} \\left(- D + r\\right) \\left(\\begin{cases} \\frac{\\operatorname{erf}{\\left(\\frac{\\sqrt{2} \\left(\\mu_{google} - \\tau\\right)}{2 \\sigma_{google}} \\right)}}{2} + \\frac{1}{2} & \\text{for}\\: \\left(\\left|{\\arg{\\left(\\sigma_{google} \\right)}}\\right| \\leq \\frac{\\pi}{4} \\wedge \\left|{2 \\arg{\\left(\\mu_{google} \\right)} - 4 \\arg{\\left(\\sigma_{google} \\right)} + 2 \\arg{\\left(\\frac{\\mu_{google} - \\tau}{\\mu_{google}} \\right)} + 2 \\pi}\\right| < \\pi\\right) \\vee \\left|{\\arg{\\left(\\sigma_{google} \\right)}}\\right| < \\frac{\\pi}{4} \\\\\\frac{\\sqrt{2} \\int\\limits_{0}^{\\infty} e^{- \\frac{\\left(z - \\mu_{google} + \\tau\\right)^{2}}{2 \\sigma_{google}^{2}}}\\, dz}{2 \\sqrt{\\pi} \\sigma_{google}} & \\text{otherwise} \\end{cases}\\right)$" + }, + "metadata": {}, + "execution_count": 5 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Local volatility\n", + "$\\sigma^2\\left(K, T, S_0\\right)=\\frac{\\frac{\\partial C}{\\partial T}}{\\frac{1}{2} K^2 \\frac{\\partial^2 C}{\\partial K^2}}$\n", + "\n", + "The right-hand side of this equation can be computed from known European option prices. So, given a complete set of European option prices for all strikes and expirations, local volatilities are given uniquely by equation above.\n", + "We can view this equation as a definition of the local volatility function regardless of what kind of process (stochastic volatility for example) actually governs the evolution of volatility." + ], + "metadata": { + "id": "tLMrOdpH8ci9" + } + }, + { + "cell_type": "code", + "source": [ + "local_volatility = smp.sqrt( dCdT / (1/2*K**2 * varphi))\n", + "local_volatility" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 141 + }, + "id": "6-ut-zDY8aMU", + "outputId": "7696d056-f450-4e39-e466-d3f270052379" + }, + "execution_count": 6, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "sqrt(zoo*S_T*(-D + r)*Piecewise((erf(sqrt(2)*(mu_google - tau)/(2*sigma_google))/2 + 1/2, (Abs(arg(sigma_google)) < pi/4) | ((Abs(arg(sigma_google)) <= pi/4) & (Abs(2*arg(mu_google) - 4*arg(sigma_google) + 2*arg((mu_google - tau)/mu_google) + 2*pi) < pi))), (sqrt(2)*Integral(exp(-(_z - mu_google + tau)**2/(2*sigma_google**2)), (_z, 0, oo))/(2*sqrt(pi)*sigma_google), True)))" + ], + "text/latex": "$\\displaystyle \\sqrt{\\tilde{\\infty} S_{T} \\left(- D + r\\right) \\left(\\begin{cases} \\frac{\\operatorname{erf}{\\left(\\frac{\\sqrt{2} \\left(\\mu_{google} - \\tau\\right)}{2 \\sigma_{google}} \\right)}}{2} + \\frac{1}{2} & \\text{for}\\: \\left(\\left|{\\arg{\\left(\\sigma_{google} \\right)}}\\right| \\leq \\frac{\\pi}{4} \\wedge \\left|{2 \\arg{\\left(\\mu_{google} \\right)} - 4 \\arg{\\left(\\sigma_{google} \\right)} + 2 \\arg{\\left(\\frac{\\mu_{google} - \\tau}{\\mu_{google}} \\right)} + 2 \\pi}\\right| < \\pi\\right) \\vee \\left|{\\arg{\\left(\\sigma_{google} \\right)}}\\right| < \\frac{\\pi}{4} \\\\\\frac{\\sqrt{2} \\int\\limits_{0}^{\\infty} e^{- \\frac{\\left(z - \\mu_{google} + \\tau\\right)^{2}}{2 \\sigma_{google}^{2}}}\\, dz}{2 \\sqrt{\\pi} \\sigma_{google}} & \\text{otherwise} \\end{cases}\\right)}$" + }, + "metadata": {}, + "execution_count": 6 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Using real values from QuantBook to calculate Local Volatility" + ], + "metadata": { + "id": "JSHTTZeU9ONk" + } + }, + { + "cell_type": "code", + "source": [ + "# QuantBook Analysis Tool \n", + "# For more information see [https://www.quantconnect.com/docs/v2/our-platform/research/getting-started]\n", + "import datetime\n", + "qb = QuantBook()\n", + "option = qb.AddOption(\"GOOG\") \n", + "option.SetFilter(-2, 2, 0, 90)\n", + "history = qb.GetOptionHistory(option.Symbol, datetime(2023, 5, 7), datetime(2023, 5, 26))\n", + "\n", + "all_history = history.GetAllData()\n", + "expiries = history.GetExpiryDates() \n", + "strikes = history.GetStrikes()\n", + "\n", + "goog = qb.AddEquity(\"GOOG\")\n", + "goog_df = qb.History(qb.Securities.Keys, datetime(2023, 5, 7), datetime(2023, 5, 26), Resolution.Daily)\n", + "goog_df = goog_df['close'].unstack(level=0)\n", + "S_T = goog_df.values[-1]\n", + "all_history.head()" + ], + "metadata": { + "id": "pYG7bmQv88tI" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#BSM model of Call option\n", + "import math\n", + "import sympy.stats\n", + "\n", + "####################### parameters #########################\n", + "tau = 3/220 #expiry the 5/19\n", + "r = 0.0341 \n", + "D = 0.0\n", + "K = strikes[-1]\n", + "sigma = goog_df.std()\n", + "S_T = float(goog_df.values[-1])\n", + "mu_goog = goog_df.mean()\n", + "#Calculate numerically the local volatility\n", + "local_volatility.subs([(S_T,S_T),(K,int(K)),(tau,tau),(D,D),(sigma_google,sigma),(mu_google,mu_google)]).evalf()\n" + ], + "metadata": { + "id": "lcZxAl8K8msU" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file