{
"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
}