{ "cells": [ { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "Python assignment\n", "---" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "zanadu": { "code_type": "" } }, "outputs": [], "source": [ "import numpy as np\n", "from scipy.stats import norm" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "# The Black-Scholes formula" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "The Black–Scholes model is one of the cornerstone of mathematical finance.\n", "The value of a European Call option for a non-dividend-paying stock $(S_t)_{t\\geq 0}$ in this model is given, at time $t\\in [0,T]$, by\n", "$$\n", "C(S_t, t, K, T) := \\mathrm{e}^{-r(T-t)}\\mathbb{E}\\left[\\max(S_{T} - K, 0)\\right]\n", " = S_t\\left(\\mathcal{N}(d_{+}) - \\mathrm{e}^{k}\\mathcal{N}(d_{-})\\right),\n", "$$\n", "where\n", "$$\n", "d_{\\pm} = \\frac{-k}{\\sigma\\sqrt{T - t}} \\pm\\frac{\\sigma\\sqrt{T-t}}{2}.\n", "$$\n", "Correspondingly, the price of a corresponding put option based on put–call parity is:\n", "$$\n", "P(S_t, t, K, T) := \\mathrm{e}^{-r(T-t)}\\mathbb{E}\\left[\\max(K-S_{T}, 0)\\right]\n", " = S_t\\left(\\mathrm{e}^{k}\\mathcal{N}(-d_{-}) - \\mathcal{N}(-d_{+})\\right).\n", "$$\n", "For both, as :
\n", "$k := \\frac{K}{S_t\\mathrm{e}^{r(T - t)}}$ is called the log-forward moneyness,
\n", "$\\mathcal{N}(\\cdot)$ is the cumulative distribution function of the standard normal distribution,
\n", "$T - t$ is the time to maturity,
\n", "$S_t$ is the spot price of the underlying asset,
\n", "$K$ is the strike price,
\n", "$r$ is the risk free rate (annual rate, expressed in terms of continuous compounding),
\n", "$\\sigma$ is the volatility of returns of the underlying asset.\n" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "### Definition of the Black-Scholes class" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "zanadu": { "code_type": "", "is_hidden": true } }, "outputs": [], "source": [ "class callOptionBs(object):\n", " ''' European call option in the Black-Scholes model.\n", " CP: ## True for a Call, False for a Put\n", " S0: ## Initial value of the stock price\n", " t0: ## Initial time\n", " r: ## instantaneous risk-free rate\n", " K: ## Strike\n", " T: ## Maturity (>t)\n", " sigma: ## volatility parameter\n", "Methods\n", "=======\n", "price : float, returns the price of the option in the Black-Scholes model\n", "impVol: float. returns the implied volatility given an option price\n", "'''\n", "\n", " def __init__(self, CP, S0, t0, r, K, T, sigma):\n", " self.CP = CP\n", " self.S0 = S0\n", " self.t0 = t0\n", " self.r = r\n", " self.K = K\n", " self.T = T\n", " self.sigma = sigma\n", " \n", " def price(self):\n", " tau = self.T-self.t0\n", " sigmtau = self.sigma*np.sqrt(self.T)\n", " k = np.log(self.K/self.S0) - self.r*tau\n", " dp = -k/sigmtau + 0.5*sigmtau\n", " dm = dp - sigmtau\n", " if self.CP: \n", " return self.S0*(norm.cdf(dp) - np.exp(k)*norm.cdf(dm))\n", " else: \n", " return self.S0*(np.exp(k)*norm.cdf(-dm) - norm.cdf(-dp))\n", " \n", " def vega(self): ## returns the Vega of the option, i.e. its derivative with respect to the volatility parameter sigma\n", " tau = self.T-self.t0\n", " sigmtau = self.sigma*np.sqrt(self.T)\n", " k = np.log(self.K/self.S0) - self.r*tau\n", " dp = -k/sigmtau + 0.5*sigmtau\n", " dm = dp - sigmtau\n", " return self.S0 * norm.cdf(dp, 0., 1.) * np.sqrt(tau)\n", "\n", " def impVol(self, obsPrice, sigma0=0.2, nbIter=100):\n", " option = callOptionBs(self.CP, self.S0, self.t0, self.r, self.K, self.T, sigma0)\n", " for _ in range(nbIter):\n", " option.sigma -= (option.price() - obsPrice) / option.vega()\n", " return option.sigma" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "zanadu": { "code_type": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Price = 8.02135223514317\n", "Vega = 54.22283335848053\n", "Implied vol = 0.17426990587356414\n" ] } ], "source": [ "CP, S0, t0, K, T, r, sigma = True, 100., 0., 105., 1.0, 0.05, 0.2\n", "option = callOptionBs(CP, S0, t0, r, K, T, sigma)\n", "print(type(option))\n", "print(\"Price = \", option.price())\n", "print(\"Vega = \", option.vega())\n", "print(\"Implied vol = \", option.impVol(obsPrice=7.))" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "## Simulating the Black-Scholes model" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "We shall always consider here $t_0=0$ for simplicity.\n", "The main idea about Monte Carlo methods is to simulate many paths of the dynamics of the process $(S_t)_{t\\in [0,T]}$, to compute the payoff $\\max(S_T-K, 0)$ at maturity $T$ for each simulation, and then average over all this value. One should not, eventually forget the discounting factor $e^{-rT}$. \n", "The Black-Scholes model in fact corresponds to continuous-time dynamics for the stock price. \n", "We introduce the following discretised version:
\n", "\n", "$$\n", "S_{t_{i+1}} = S_{t_i}\\left(1+r \\delta + \\sigma\\sqrt{\\delta}\\widetilde{n}_i\\right),\n", "$$\n", "
\n", "where $t_i = i\\delta$ with $\\delta:=T/N$, so that the sequence $\\{t_0,t_1,\\ldots,t_{N-1}\\}$ represents a partition of the interval $[0,T]$ for some integer $N$.\n", "Here, the sequence $(\\widetilde{n}_0,\\ldots,\\widetilde{n}_{N-1})$ is an iid sequence of centered Gaussian random variables with unit variance.\n", "Denote by $S_{t_{N}}^j = S_T^j$ the $j$-th simulation (for $j=1,\\ldots,M$) of the terminal value of the stock price, according to the scheme above.\n" ] }, { "cell_type": "markdown", "metadata": { "zanadu": {} }, "source": [ "(i) Using pure Python loops (over $N$ and $M$), construct a function $\\Phi_1$ giving the price of the European Call option as a function of the Black-Scholes parameters and of $M$ and $N$.
\n", "(ii) Construct a similar function $\\Phi_2$ avoiding (the very slow) Python loops altogether. Hint: the Numpy function np.cumsum could come in handy.
\n", "(iii) Construct another function $\\Phi_3$ using pandas DataFrames.
\n", "(iv) For fixed $N$, say $N=100$, plot the convergence of the three functions to the exact price (given by the analytic formula above) as $M$ becomes large.
\n", "(iv) Compare the performance (in terms of computation time) of the three functions $\\Phi_1$, $\\Phi_2$, $\\Phi_3$.
\n", "
\n", "\n", "In all the numerical examples above, consider the following values:
\n", "$$\n", "S_0 = 100, \\qquad\n", "t_0 = 0, \\qquad\n", "K = 95, \\qquad\n", "T = 1, \\qquad\n", "r = 0, \\qquad\n", "\\sigma = 15\\%.\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "zanadu": { "code_type": "" } }, "outputs": [], "source": [] } ], "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.7.3" }, "zanadu": { "authors": [ "Antoine Jacquier" ], "bibliography_data": {}, "category": "1", "clearance": "Internal", "group_name": "ImperialMScMathFin", "md5": "1873d7c790cd27bdbc52bafb522224e4", "notebook_id": "8DD48E41-38EB-4E79-81D3-BF2EEE233D02", "python_name": null, "reviewer_id": "B81D4EC4-269D-42B3-9975-85BC42EE5712", "status": "Approved" } }, "nbformat": 4, "nbformat_minor": 1 }