{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Calibration, volatilité locale et stochastique - TP2\n", "(Stefano De Marco)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The SVI parameterisation\n", "\n", "#### $\\blacktriangleright$ Implied total variance\n", "\n", "We recall that the implied total variance $w(T,k)$ for maturity $T$ and log-forward moneyness $k$ is defined by\n", "\n", "$$\n", "w(T,k) = T \\, \\hat \\sigma^2(T, k)\n", "$$\n", "\n", "where $\\hat \\sigma(T, k)$ denotes the implied volatility for the same maturity and log-forward moneyness.\n", "\n", "\n", "#### $\\blacktriangleright$ The SVI (Stochastic Volatility Inspired) parameterisation\n", "\n", "SVI is a parametric family of functions proposed by [Gatheral 2004] to model the total implied variance smile for a fixed maturity $T$ :\n", "\n", "\\begin{equation}\n", "w_\\mathrm{SVI}(k) = a + b \\left(\\rho (k - \\bar k) + \\sqrt{(k - \\bar k)^2 + \\sigma^2} \\right),\n", "\\qquad k \\in \\mathbb R,\n", "\\end{equation}\n", "\n", "where the parameters $(a, b, \\rho, \\bar k, \\sigma)$ satisfy\n", "\n", "$$\n", "b > 0 \\qquad \\quad \\rho \\in [-1,1] \\qquad \\quad \\sigma > 0, \\qquad \\quad a, \\bar k \\in \\mathbb R.\n", "$$\n", "\n", "Note that, since $\\min_{k \\in \\mathbb R} w_\\mathrm{SVI}(k) = a + b \\, \\sigma \\, \\sqrt{1 - \\rho^2}$, we also have to impose the condition\n", "\n", "$$\n", "a + b \\, \\sigma \\, \\sqrt{1 - \\rho^2} > 0\n", "$$\n", "\n", "in order to grant positivity of the function $w_\\mathrm{SVI}$.\n", "\n", "Let us define the SVI function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "def SVI(k, a, b , rho, k_bar, sig):\n", " total_variance = a + b*( rho*(k - k_bar) + np.sqrt( (k - k_bar)*(k - k_bar) + sig*sig) )\n", " \n", " return total_variance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And let us have a look at a few examples of SVI functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "########################\n", "# A parameter set\n", "########################\n", "a = 0.04\n", "b = 0.4\n", "rho = -0.7\n", "k_bar = 0.1\n", "sig = 0.2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "################################################\n", "# We define a test for the positivity condition\n", "################################################\n", "def test_positivity(a, b, rho):\n", " assert(rho <= 1); assert(-1 <= rho)\n", " assert(b > 0)\n", " \n", " ## We check the positivity of the minimum \n", " minimum = a + b*sig*np.sqrt(1 - rho*rho)\n", " assert(minimum > 0)\n", " \n", " print(\"\\n Positivity test: OK \\n\")\n", " \n", " return 0\n", "\n", "test_positivity(a, b, rho)\n", "\n", "#############################################\n", "## TO DO: initialise and plot a SVI slice\n", "#############################################\n", "log_moneyness = np.linspace(-0.5, 0.5, 200)\n", "\n", "total_variances = ????\n", "\n", "plt.plot( ???? , ???? , color='royalblue', linewidth=1.5, \\\n", " label=r\"$a$=%1.2f, $b$=%1.2f, $\\rho$=%1.2f, $k_{bar}$=%1.2f, $\\sigma$=%1.2f\" %(a,b,rho,k_bar,sig))\n", "\n", "plt.axvline(0., linestyle=\"--\")\n", "\n", "plt.xlabel(\"log-forward moneyness $k$\", fontsize=14)\n", "plt.ylabel(\"Total variance\", fontsize=14)\n", "plt.title(r\"SVI parameterisation (implied variance)\", fontsize=18)\n", "\n", "plt.legend(loc=9, fontsize=14, bbox_to_anchor=(1.6, 1.0), ncol=1)\n", "\n", "#######################################################\n", "## TO DO: plot the corresponding SVI total implied vol\n", "#######################################################\n", "total_vols = ????\n", "\n", "plt.figure()\n", "\n", "plt.plot( ???? , ???? , color=\"indianred\", linewidth=1.5, \\\n", " label=r\"$a$=%1.2f, $b$=%1.2f, $\\rho$=%1.2f, $k_{bar}$=%1.2f, $\\sigma$=%1.2f\" %(a,b,rho,k_bar,sig))\n", "\n", "plt.axvline(0., linestyle=\"--\")\n", "\n", "plt.xlabel(\"log-forward moneyness $k$\", fontsize=14)\n", "plt.ylabel(\"Total volatility\", fontsize=14)\n", "plt.title(r\"SVI parameterisation (implied volatility)\", fontsize=18)\n", "\n", "plt.legend(loc=9, fontsize=14, bbox_to_anchor=(1.6, 1.0), ncol=1)\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ The sufficient conditions for the absence of static arbitrage on an SVI slice\n", "\n", "The two conditions \n", "\n", "1) $$\n", "g_{\\mathrm{SVI}}(k)= \n", "\\frac 12 w''_{\\mathrm{SVI}}(k)\n", "+ \\left(1-\\frac{k \\, w'_{\\mathrm{SVI}}(k)}{2 \\, w_{\\mathrm{SVI}}(k)}\\right)^2 -\\frac {(w'_{\\mathrm{SVI}}(k))^2}{4}\\left(\\frac 1{w_{\\mathrm{SVI}}(k)}+\\frac{1}{4}\\right) \\geq 0,\n", "\\qquad \\forall k \\in \\mathbb R\n", "$$\n", "\n", "3) $$\n", "\\lim_{k \\to \\infty} d_1 \\left(k, w_{\\mathrm{SVI}}(k) \\right) \n", "= \\lim_{k \\to \\infty} \\left( -\\frac k{\\sqrt{w_{\\mathrm{SVI}}(k)}} + \\frac 12 {\\sqrt{w_{\\mathrm{SVI}}(k)}} \\right) = -\\infty\n", "$$\n", "\n", "grant the absence of arbitrage on the SVI parameterisation $w_{\\mathrm{SVI}}(\\cdot)$ (for the fixed maturity $T$).\n", "\n", "More precisely: \n", "+ Condition 1 is equivalent to the convexity of the corresponding call price with respect to strike\n", "\n", "$$\\partial_{KK} Call(T, K) \\ge 0 \\qquad \\mbox{ for every } K \\ge 0, $$\n", "\n", "where $Call(T, K) = Call_{BS}\\Bigl(T, K, \\sigma = \\frac 1{\\sqrt T}\\sqrt{ w_{\\mathrm{SVI}}(\\log K/F_0^T) } \\Bigr)$.\n", "\n", "+ Condition 3 is equivalent to $$\\lim_{K \\to \\infty} Call(T, K) = 0$$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ TO DO Condition 3: show (with a computation) that the condition $\\lim_{k \\to \\infty} d_1(k,w(k)) = -\\infty$ is satisfied for a function $w(\\cdot)$ if\n", "\n", "$$\n", "\\limsup_{k \\to \\infty} \\frac{w(k)}{2 k} < 1.\n", "$$\n", "\n", "#### Compute $\\lim_{k \\to \\infty} \\frac{w_{\\mathrm{SVI}}(k)}k$ and infer that the SVI parameterisation $w_{\\mathrm{SVI}}$ satisfies condition 3 as soon as\n", "$$ b (1 + \\rho) < 2. $$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "############################################################\n", "# We define a test for the asymptotic slope condition above\n", "############################################################\n", "def test_slope(b, rho):\n", " right_slope = b * (1 + rho)\n", " \n", " print(\"\\n Right slope b*(1 + rho) = %1.2f\" %right_slope)\n", " \n", " assert(right_slope <= 2)\n", " \n", " pass\n", "\n", "test_slope(b, rho)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ TO DO Condition 1: we accept to test it numerically\n", "\n", "We will use the formulas\n", "\n", "$$\n", "\\begin{aligned}\n", "&w'_\\mathrm{SVI}(k) = b \\rho + b \\frac{k - \\bar k}{\\sqrt{(k - \\bar k)^2 + \\sigma^2}}\n", "\\\\\n", "&w''_\\mathrm{SVI}(k) = \\frac{b \\, \\sigma^2}{\\left((k - \\bar k)^2 + \\sigma^2\\right)^{3/2}}\n", "\\end{aligned}\n", "$$\n", "\n", "$$\n", "g_{\\mathrm{SVI}}(k)= \n", "\\frac 12 w''_{\\mathrm{SVI}}(k)\n", "+ \\left(1-\\frac{k \\, w'_{\\mathrm{SVI}}(k)}{2 \\, w_{\\mathrm{SVI}}(k)}\\right)^2 -\\frac {(w'_{\\mathrm{SVI}}(k))^2}{4}\\left(\\frac 1{w_{\\mathrm{SVI}}(k)}+\\frac{1}{4}\\right) \\geq 0,\n", "\\qquad \\forall k \\in \\mathbb R\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#####################################\n", "# On teste la condition de convexité \n", "#####################################\n", "def test_convexity(k, a, b, rho, k_bar, sig):\n", " square_rt = np.sqrt( (k - k_bar)*(k - k_bar) + sig*sig)\n", " \n", " w = SVI(k, a, b, rho, k_bar, sig)\n", " \n", " first_der_w = b*rho + b*(k - k_bar) / square_rt\n", " \n", " second_der_w = b*sig*sig / (square_rt**3)\n", " \n", " ################################################################\n", " ## TO DO: evaluate the function g_SVI(k) defined in Condition 1 \n", " ################################################################\n", " \n", " g = ????\n", " \n", " return g\n", "\n", "##################################################\n", "## TO DO: complete the following function, which\n", "## evaluates and plot the function g_SVI(k)\n", "## on a fixed log-moneyness grid\n", "##################################################\n", "def visual_test_convexity(a, b, rho, k_bar, sig, log_mon_lower=-1, log_mon_upper=1):\n", " log_moneyness_test = np.linspace(log_mon_lower, log_mon_upper, 200)\n", "\n", " test_fct_g = ????\n", "\n", " plt.figure(figsize=(9,6))\n", "\n", " plt.plot( ???? , ???? , color=\"red\", linewidth=0., marker=\".\", markersize=4, \\\n", " label=r\"$a$=%1.2f, $b$=%1.2f, $\\rho$=%1.2f, $k_bar$=%1.2f, $\\sigma$=%1.2f\" %(a,b,rho,k_bar,sig))\n", "\n", " plt.axhline(0., linestyle=\"--\", color=\"k\")\n", "\n", " plt.xlabel(\"log-forward moneyness $k$\", fontsize=14)\n", " plt.ylabel(\"Function g(k) in Condition 1\", fontsize=14)\n", " plt.title(r\"Test of butterfly arbitrage for SVI parameterisation\", fontsize=18)\n", "\n", " plt.legend(loc=9, fontsize=14, bbox_to_anchor=(1.6, 1.0), ncol=1)\n", "\n", " plt.show()\n", " \n", " assert( (test_fct_g >= 0).all )\n", " \n", " pass\n", " \n", "visual_test_convexity(a, b, rho, k_bar, sig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$\\blacktriangleright$ Now consider and test the following parameter set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = -0.041\n", "b = 0.12\n", "rho = 0.3586\n", "k_bar = 0.3060\n", "sig = 0.4153" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The calibration example to option data on CAC40 " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us reload the option quotes on the CAC40 index we used in TP1: date = 2 October 2013, maturity = 20 June 2014.\n", "\n", "We import the quotes for strikes, mid call and mid put prices from the csv file, and use the values of forward, discount factor, and optimal SVI parameters we have already found in TP1. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "quotes = np.loadtxt(open(\"CAC40_last_option_quotes_2_oct_2013_maturity_20_june_2014.csv\", \"r\"), delimiter=\",\", skiprows=1)\n", "\n", "strikes = quotes[:, 0]\n", "call_prices = quotes[:, 1]\n", "put_prices = quotes[:, 2]\n", "\n", "maturity = 258./365\n", "\n", "#################################################\n", "## Discount factor D(0,T) and forward value F(0,T)\n", "## inferred from put-call parity\n", "#################################################\n", "DF_T = 0.9981\n", "\n", "F_T = 4035.0\n", "\n", "#####################################\n", "## Implied vol from puts and calls\n", "#####################################\n", "from BlackScholesFunctions import volImplCore_Newton\n", "\n", "vols_puts = []\n", "vols_calls = []\n", "\n", "for i in range(strikes.size):\n", " K = strikes[i]\n", " \n", " vol_put, iterations = volImplCore_Newton(tau = maturity, K = K, DF = DF_T,\n", " F = F_T,\n", " price = put_prices[i],\n", " CallOrPutFlag = 0,\n", " initial_point='automatic', prix_tol = 1.e-4, max_iter=50)\n", " \n", " vol_call, iterations = volImplCore_Newton(tau = maturity, K = K, DF = DF_T,\n", " F = F_T,\n", " price = call_prices[i],\n", " CallOrPutFlag = 1,\n", " initial_point='automatic', prix_tol = 1.e-4, max_iter=50)\n", " \n", " vols_puts = np.append(vols_puts, vol_put)\n", " vols_calls = np.append(vols_calls, vol_call)\n", "\n", "##################################################################\n", "## The SVI parameters calibrated via least-square minimimization\n", "##################################################################\n", " \n", "a_star, b_star, rho_star, k_bar_star, sig_star = 0.00437422, 0.06119395, -0.41290848, 0.16507814, 0.18949317" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$\\blacktriangleright$ The resulting market smile and SVI smile:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(9,6))\n", "\n", "mkt_log_mon = ????\n", "\n", "###########\n", "# Market\n", "###########\n", "plt.plot(???? , ???? ,\n", " color='indianred', linewidth=0., marker=\".\", markersize=12, label=\"Implied vol puts\")\n", "\n", "plt.plot(???? , ???? ,\n", " color='royalblue', linewidth=0., marker=\"+\", markersize=14, label=\"Implied vol calls\")\n", "\n", "##########\n", "# Model\n", "##########\n", "total_variances_fit = ????\n", "\n", "implied_vols_fit = ????\n", "\n", "plt.plot(???? , ???? ,\n", " color='royalblue', linewidth=1.5, \n", " label=r\"$a$=%1.3f, $b$=%1.2f, $\\rho$=%1.2f, $k_{bar}$=%1.2f, $\\sigma$=%1.2f\" %(a_star, b_star, rho_star, k_bar_star, sig_star))\n", "\n", "plt.xlabel(r\"log-forward moneyness $k$\", fontsize=14)\n", "plt.ylabel(\"Implied vol\", fontsize=14)\n", "plt.title(\"Market implied vol and SVI fit\", fontsize=18)\n", "\n", "plt.legend(loc=9, fontsize=14, bbox_to_anchor=(1.4, 1.0), ncol=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ TO DO: check a posteriori the butterfly arbitrage conditions on the calibrated SVI" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## TO DO: test positivity\n", "\n", "## TO DO: test the no-butterfly arbitrage condition (slope + convexity)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ The no arbitrage condition is satisfied for the calibrated SVI slice above...\n", "\n", "..but as we have seen in one of the examples, this might fail to be the case even for parameter sets that produce a reasonable-looking implied volatility smile.\n", "\n", "A possible (even if not perfect) __solution__ to avoid such cases is to calibrate a SVI parameterisation while _penalizing_ the parameters that violate the no arbitrage condition:\n", "\n", "$$\n", "\\min_{(a, \\, b, \\, \\rho, \\, \\bar k, \\, \\sigma)}\n", "\\biggl(\n", "\\sum_{i} \\left( w^{\\mathrm{mkt}}(k_i) - w_\\mathrm{SVI} \\left(k_i \\, ; a,b,\\rho,\\bar k,\\sigma \\right) \\right)^2\n", "+ \\lambda \\sum_{k \\in \\pi} \\Bigl( g_\\mathrm{SVI} \\left(k \\, ; a,b,\\rho,\\bar k,\\sigma \\right)^- \\Bigr)^2\n", "\\biggr)\n", "$$\n", "\n", "where:\n", "\n", "+ $k_i$ are the observed market values of log-moneyness\n", "+ $\\pi$ is some pre-defined grid of log-moneyness values (possibly larger that the set $\\{k_i\\}_i$)\n", "+ $x^-$ denotes the negative part. \n", "\n", "In the problem above, $\\lambda > 0$ is an additional penalization parameter to be chosen by the user.\n", "Of course, the choice of $\\lambda$ affects the final result. On the one hand, would like to take $\\lambda$ large, so that the violation of the no-arbitrage condition is strongly penalised.\n", "On the other, we would like to take $\\lambda$ small, so that the minimization problem above is close to the original one (least square calibration of unconstrained SVI). \n", "\n", "In general, the choice of $\\lambda$ for a given calibration problem might not be obvious.\n", "\n", "$\\blacktriangleright$ We present below an alternative solution that allows to formulate the no-arbitrage constraints in terms of simple and explicitly checkable conditions in the parameter space: another parameterisation, called SSVI.\n", "The price to pay for this advantage is a slightly weaker calibration power." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A possible solution that avoids arbitrage and that parametrizes the whole surface: the SSVI (\"surface SVI\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far, we have worked with one single maturity $T$.\n", "\n", "How to extend the to several maturities?\n", "\n", "A solution is provided by the SSVI parameterisation of [Gatheral and Jacquier, Quantitative Finance 14, 2014], constructed as follows: first define the\n", "\n", "### SSVI slice\n", "\n", "$$\n", "w_\\mathrm{SSVI}(k) = \\frac{\\theta} 2 \\Bigl(1 + \\varphi \\, \\rho k + \\sqrt{(\\varphi \\, k + \\rho)^2 + 1 - \\rho^2} \\Bigr),\n", "\\qquad k \\in \\mathbb R\n", "$$\n", "\n", "where \n", "\n", "$$\n", "\\theta \\ge 0, \\qquad \\varphi \\ge 0, \\qquad \\rho \\in (-1,1).\n", "$$\n", "\n", "Note that $w_\\mathrm{SSVI}(k)|_{k=0} = \\theta$, so that the parameter $\\theta$ corresponds to the ATM total variance." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is possible to see that the SSVI slice $w_\\mathrm{SSVI}$ is a special case of the original SVI $w_\\mathrm{SVI}(k)$, parameterised by __three parameters instead of five__: the mapping SSVI to SVI is given by \n", "\n", "$$\n", "w_\\mathrm{SSVI}(k; \\theta, \\varphi, \\rho) = w_\\mathrm{SVI}(k; a,b, \\rho, k_0, \\sigma)\n", "$$\n", "where\n", "$$\n", "a = \\frac \\theta 2 (1-\\rho^2), \\quad\n", "b = \\frac{\\theta \\varphi} 2, \\quad\n", "k_0 = -\\frac \\rho \\varphi, \\quad\n", "\\sigma = \\frac{\\sqrt{1 - \\rho^2}}\\varphi.\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ The first advantage of the SSVI slice with respect to SVI (at fixed maturity):\n", "\n", "+ Thanks to the reduced complexity (3 parameters instead of 5), the no-butterfly arbitrage conditions 1) and 3) on a SSVI slice can be replaced with much simpler conditions on the parameters $(\\theta, \\varphi, \\rho)$, given in see [Gatheral and Jacquier 2014], Theorem 4.2:\n", "\n", "$$\n", "\\left\\{\n", "\\begin{aligned}\n", "&\\theta \\, \\varphi \\, (1+|\\rho|) < 4\n", "\\\\\n", "&\\theta \\, \\varphi^2 \\, (1+|\\rho|) \\le 4\n", "\\end{aligned}\n", "\\right.\n", "$$\n", "\n", "+ On the other hand, we should not forget that 3 parameters have a reduced calibration flexibility with respect to 5." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$\\blacktriangleright$ __TO DO__: Try a calibration of the SSVI slice (as opposed to SVI) to the market data above." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Full SSVI surface\n", "\n", "At this stage, we have only worked with fixed-maturity slices of the implied volatility surface. We haven't defined a function $w_\\mathrm{SSVI}$ of $(T, k)$ yet.\n", "Given\n", "\n", "+ a curve $T \\mapsto \\theta_T \\ge 0$ (representing at-the-money total implied variance for maturity $T$)\n", "\n", "+ a function $\\varphi: \\mathbb R_+ \\to \\mathbb R_+$,\n", "\n", "the __surface SVI__ is defined by\n", "\n", "$$\n", "\\begin{aligned}\n", "w_\\mathrm{SSVI}(T, k) \n", "&= \n", "w_\\mathrm{SSVI}(k; \\ \\theta_{T}, \\ \\varphi(\\theta_T), \\ \\rho) \n", "\\\\\n", "&= \\frac{\\theta_{T}} 2 \\Bigl(1 + \\varphi(\\theta_{T}) \\rho k + \\sqrt{(\\varphi(\\theta_{T}) k + \\rho)^2 + 1 - \\rho^2} \\Bigr),\n", "\\qquad k \\in \\mathbb R, T > 0.\n", "\\end{aligned}\n", "$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### $\\blacktriangleright$ The second advantage:\n", "\n", "The no-calendar spread arbitrage condition\n", "\n", "2) The function $\\tau \\mapsto w_\\mathrm{SSVI}(\\tau, k) $ is increasing, for every $k \\in \\mathbb R$\n", "\n", "can also be reformulated in terms of a simple condition on the functions $\\theta_\\cdot$ and $\\varphi(\\cdot)$, see [Gatheral and Jacquier 2014], Theorem 4.1:\n", "\n", "$$\n", "\\left\\{\n", "\\begin{aligned}\n", "&T \\mapsto \\theta_T \\mbox{ is increasing }\n", "\\\\\n", "&0 \n", "\\le\n", "\\partial_\\theta (\\theta \\, \\varphi(\\theta))\n", "\\le\n", "\\frac1{\\rho^2} \\left(1+ \\sqrt{1-\\rho^2}\\right) \\varphi(\\theta) \\qquad \\forall \\theta,\n", "\\end{aligned}\n", "\\right.\n", "$$\n", "\n", "where we have supposed that the functions $\\theta_\\cdot$ and $\\varphi(\\cdot)$ are differentiable in the second line." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.5" } }, "nbformat": 4, "nbformat_minor": 2 }