"
]
},
{
"cell_type": "markdown",
"id": "661a2c14",
"metadata": {},
"source": [
"# SciPy\n",
"\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "62fcfb59",
"metadata": {},
"source": [
"## Contents\n",
"\n",
"- [SciPy](#SciPy) \n",
" - [Overview](#Overview) \n",
" - [SciPy versus NumPy](#SciPy-versus-NumPy) \n",
" - [Statistics](#Statistics) \n",
" - [Roots and Fixed Points](#Roots-and-Fixed-Points) \n",
" - [Optimization](#Optimization) \n",
" - [Integration](#Integration) \n",
" - [Linear Algebra](#Linear-Algebra) \n",
" - [Exercises](#Exercises) "
]
},
{
"cell_type": "markdown",
"id": "fc3617ae",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"[SciPy](http://www.scipy.org) builds on top of NumPy to provide common tools for scientific programming such as\n",
"\n",
"- [linear algebra](http://docs.scipy.org/doc/scipy/reference/linalg.html) \n",
"- [numerical integration](http://docs.scipy.org/doc/scipy/reference/integrate.html) \n",
"- [interpolation](http://docs.scipy.org/doc/scipy/reference/interpolate.html) \n",
"- [optimization](http://docs.scipy.org/doc/scipy/reference/optimize.html) \n",
"- [distributions and random number generation](http://docs.scipy.org/doc/scipy/reference/stats.html) \n",
"- [signal processing](http://docs.scipy.org/doc/scipy/reference/signal.html) \n",
"- etc., etc \n",
"\n",
"\n",
"Like NumPy, SciPy is stable, mature and widely used.\n",
"\n",
"Many SciPy routines are thin wrappers around industry-standard Fortran libraries such as [LAPACK](https://en.wikipedia.org/wiki/LAPACK), [BLAS](https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms), etc.\n",
"\n",
"It’s not really necessary to “learn” SciPy as a whole.\n",
"\n",
"A more common approach is to get some idea of what’s in the library and then look up [documentation](http://docs.scipy.org/doc/scipy/reference/index.html) as required.\n",
"\n",
"In this lecture, we aim only to highlight some useful parts of the package."
]
},
{
"cell_type": "markdown",
"id": "217d165a",
"metadata": {},
"source": [
"## SciPy versus NumPy\n",
"\n",
"SciPy is a package that contains various tools that are built on top of NumPy, using its array data type and related functionality.\n",
"\n",
"In fact, when we import SciPy we also get NumPy, as can be seen from this excerpt the SciPy initialization file:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba38047e",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"# Import numpy symbols to scipy namespace\n",
"from numpy import *\n",
"from numpy.random import rand, randn\n",
"from numpy.fft import fft, ifft\n",
"from numpy.lib.scimath import *"
]
},
{
"cell_type": "markdown",
"id": "c1541981",
"metadata": {},
"source": [
"However, it’s more common and better practice to use NumPy functionality explicitly."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77f82b7a",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"a = np.identity(3)"
]
},
{
"cell_type": "markdown",
"id": "d34adbcd",
"metadata": {},
"source": [
"What is useful in SciPy is the functionality in its sub-packages\n",
"\n",
"- `scipy.optimize`, `scipy.integrate`, `scipy.stats`, etc. \n",
"\n",
"\n",
"Let’s explore some of the major sub-packages."
]
},
{
"cell_type": "markdown",
"id": "dd61e027",
"metadata": {},
"source": [
"## Statistics\n",
"\n",
"\n",
"\n",
"The `scipy.stats` subpackage supplies\n",
"\n",
"- numerous random variable objects (densities, cumulative distributions, random sampling, etc.) \n",
"- some estimation procedures \n",
"- some statistical tests "
]
},
{
"cell_type": "markdown",
"id": "938b98be",
"metadata": {},
"source": [
"### Random Variables and Distributions\n",
"\n",
"Recall that `numpy.random` provides functions for generating random variables"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2835674a",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"np.random.beta(5, 5, size=3)"
]
},
{
"cell_type": "markdown",
"id": "90d33417",
"metadata": {},
"source": [
"This generates a draw from the distribution with the density function below when `a, b = 5, 5`\n",
"\n",
"\n",
"\n",
"$$\n",
"f(x; a, b) = \\frac{x^{(a - 1)} (1 - x)^{(b - 1)}}\n",
" {\\int_0^1 u^{(a - 1)} (1 - u)^{(b - 1)} du}\n",
" \\qquad (0 \\leq x \\leq 1) \\tag{13.1}\n",
"$$\n",
"\n",
"Sometimes we need access to the density itself, or the cdf, the quantiles, etc.\n",
"\n",
"For this, we can use `scipy.stats`, which provides all of this functionality as well as random number generation in a single consistent interface.\n",
"\n",
"Here’s an example of usage"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95dff1bc",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"%matplotlib inline\n",
"from scipy.stats import beta\n",
"import matplotlib.pyplot as plt\n",
"plt.rcParams['figure.figsize'] = (10,6)\n",
"\n",
"q = beta(5, 5) # Beta(a, b), with a = b = 5\n",
"obs = q.rvs(2000) # 2000 observations\n",
"grid = np.linspace(0.01, 0.99, 100)\n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.hist(obs, bins=40, density=True)\n",
"ax.plot(grid, q.pdf(grid), 'k-', linewidth=2)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "e48152f9",
"metadata": {},
"source": [
"The object `q` that represents the distribution has additional useful methods, including"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "447fb993",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"q.cdf(0.4) # Cumulative distribution function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9173e82c",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"q.ppf(0.8) # Quantile (inverse cdf) function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6274e4da",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"q.mean()"
]
},
{
"cell_type": "markdown",
"id": "47c8c16d",
"metadata": {},
"source": [
"The general syntax for creating these objects that represent distributions (of type `rv_frozen`) is\n",
"\n",
"> `name = scipy.stats.distribution_name(shape_parameters, loc=c, scale=d)`\n",
"\n",
"\n",
"Here `distribution_name` is one of the distribution names in [scipy.stats](http://docs.scipy.org/doc/scipy/reference/stats.html).\n",
"\n",
"The `loc` and `scale` parameters transform the original random variable\n",
"$ X $ into $ Y = c + d X $."
]
},
{
"cell_type": "markdown",
"id": "4353e109",
"metadata": {},
"source": [
"### Alternative Syntax\n",
"\n",
"There is an alternative way of calling the methods described above.\n",
"\n",
"For example, the code that generates the figure above can be replaced by"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05f04c0e",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"obs = beta.rvs(5, 5, size=2000)\n",
"grid = np.linspace(0.01, 0.99, 100)\n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.hist(obs, bins=40, density=True)\n",
"ax.plot(grid, beta.pdf(grid, 5, 5), 'k-', linewidth=2)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "7e3c349e",
"metadata": {},
"source": [
"### Other Goodies in scipy.stats\n",
"\n",
"There are a variety of statistical functions in `scipy.stats`.\n",
"\n",
"For example, `scipy.stats.linregress` implements simple linear regression"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13e215ba",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.stats import linregress\n",
"\n",
"x = np.random.randn(200)\n",
"y = 2 * x + 0.1 * np.random.randn(200)\n",
"gradient, intercept, r_value, p_value, std_err = linregress(x, y)\n",
"gradient, intercept"
]
},
{
"cell_type": "markdown",
"id": "2e5820ac",
"metadata": {},
"source": [
"To see the full list, consult the [documentation](https://docs.scipy.org/doc/scipy/reference/stats.html#statistical-functions-scipy-stats)."
]
},
{
"cell_type": "markdown",
"id": "6fed4d98",
"metadata": {},
"source": [
"## Roots and Fixed Points\n",
"\n",
"A **root** or **zero** of a real function $ f $ on $ [a,b] $ is an $ x \\in [a, b] $ such that $ f(x)=0 $.\n",
"\n",
"For example, if we plot the function\n",
"\n",
"\n",
"\n",
"$$\n",
"f(x) = \\sin(4 (x - 1/4)) + x + x^{20} - 1 \\tag{13.2}\n",
"$$\n",
"\n",
"with $ x \\in [0,1] $ we get"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c864a240",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"f = lambda x: np.sin(4 * (x - 1/4)) + x + x**20 - 1\n",
"x = np.linspace(0, 1, 100)\n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.plot(x, f(x), label='$f(x)$')\n",
"ax.axhline(ls='--', c='k')\n",
"ax.set_xlabel('$x$', fontsize=12)\n",
"ax.set_ylabel('$f(x)$', fontsize=12)\n",
"ax.legend(fontsize=12)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "a96cab9f",
"metadata": {},
"source": [
"The unique root is approximately 0.408.\n",
"\n",
"Let’s consider some numerical techniques for finding roots."
]
},
{
"cell_type": "markdown",
"id": "7639c5ab",
"metadata": {},
"source": [
"### Bisection\n",
"\n",
"\n",
"\n",
"One of the most common algorithms for numerical root-finding is *bisection*.\n",
"\n",
"To understand the idea, recall the well-known game where\n",
"\n",
"- Player A thinks of a secret number between 1 and 100 \n",
"- Player B asks if it’s less than 50 \n",
" - If yes, B asks if it’s less than 25 \n",
" - If no, B asks if it’s less than 75 \n",
"\n",
"\n",
"And so on.\n",
"\n",
"This is bisection.\n",
"\n",
"Here’s a simplistic implementation of the algorithm in Python.\n",
"\n",
"It works for all sufficiently well behaved increasing continuous functions with $ f(a) < 0 < f(b) $\n",
"\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f45da809",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"def bisect(f, a, b, tol=10e-5):\n",
" \"\"\"\n",
" Implements the bisection root finding algorithm, assuming that f is a\n",
" real-valued function on [a, b] satisfying f(a) < 0 < f(b).\n",
" \"\"\"\n",
" lower, upper = a, b\n",
"\n",
" while upper - lower > tol:\n",
" middle = 0.5 * (upper + lower)\n",
" if f(middle) > 0: # root is between lower and middle\n",
" lower, upper = lower, middle\n",
" else: # root is between middle and upper\n",
" lower, upper = middle, upper\n",
"\n",
" return 0.5 * (upper + lower)"
]
},
{
"cell_type": "markdown",
"id": "48cbb306",
"metadata": {},
"source": [
"Let’s test it using the function $ f $ defined in [(13.2)](#equation-root-f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "958c0428",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"bisect(f, 0, 1)"
]
},
{
"cell_type": "markdown",
"id": "387209c2",
"metadata": {},
"source": [
"Not surprisingly, SciPy provides its own bisection function.\n",
"\n",
"Let’s test it using the same function $ f $ defined in [(13.2)](#equation-root-f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc190e2b",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.optimize import bisect\n",
"\n",
"bisect(f, 0, 1)"
]
},
{
"cell_type": "markdown",
"id": "459b55c3",
"metadata": {},
"source": [
"### The Newton-Raphson Method\n",
"\n",
"\n",
"\n",
"Another very common root-finding algorithm is the [Newton-Raphson method](https://en.wikipedia.org/wiki/Newton%27s_method).\n",
"\n",
"In SciPy this algorithm is implemented by `scipy.optimize.newton`.\n",
"\n",
"Unlike bisection, the Newton-Raphson method uses local slope information in an attempt to increase the speed of convergence.\n",
"\n",
"Let’s investigate this using the same function $ f $ defined above.\n",
"\n",
"With a suitable initial condition for the search we get convergence:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "532b40ba",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.optimize import newton\n",
"\n",
"newton(f, 0.2) # Start the search at initial condition x = 0.2"
]
},
{
"cell_type": "markdown",
"id": "d19209ed",
"metadata": {},
"source": [
"But other initial conditions lead to failure of convergence:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "746e62d3",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"newton(f, 0.7) # Start the search at x = 0.7 instead"
]
},
{
"cell_type": "markdown",
"id": "7a1d37ba",
"metadata": {},
"source": [
"### Hybrid Methods\n",
"\n",
"A general principle of numerical methods is as follows:\n",
"\n",
"- If you have specific knowledge about a given problem, you might be able to exploit it to generate efficiency. \n",
"- If not, then the choice of algorithm involves a trade-off between speed and robustness. \n",
"\n",
"\n",
"In practice, most default algorithms for root-finding, optimization and fixed points use *hybrid* methods.\n",
"\n",
"These methods typically combine a fast method with a robust method in the following manner:\n",
"\n",
"1. Attempt to use a fast method \n",
"1. Check diagnostics \n",
"1. If diagnostics are bad, then switch to a more robust algorithm \n",
"\n",
"\n",
"In `scipy.optimize`, the function `brentq` is such a hybrid method and a good default"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cac3c3b6",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.optimize import brentq\n",
"\n",
"brentq(f, 0, 1)"
]
},
{
"cell_type": "markdown",
"id": "ef061275",
"metadata": {},
"source": [
"Here the correct solution is found and the speed is better than bisection:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a7a416c7",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"%timeit brentq(f, 0, 1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7404677f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"%timeit bisect(f, 0, 1)"
]
},
{
"cell_type": "markdown",
"id": "916a27d5",
"metadata": {},
"source": [
"### Multivariate Root-Finding\n",
"\n",
"\n",
"\n",
"Use `scipy.optimize.fsolve`, a wrapper for a hybrid method in MINPACK.\n",
"\n",
"See the [documentation](http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fsolve.html) for details."
]
},
{
"cell_type": "markdown",
"id": "f4da0692",
"metadata": {},
"source": [
"### Fixed Points\n",
"\n",
"A **fixed point** of a real function $ f $ on $ [a,b] $ is an $ x \\in [a, b] $ such that $ f(x)=x $.\n",
"\n",
"\n",
"\n",
"SciPy has a function for finding (scalar) fixed points too"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39115c3b",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.optimize import fixed_point\n",
"\n",
"fixed_point(lambda x: x**2, 10.0) # 10.0 is an initial guess"
]
},
{
"cell_type": "markdown",
"id": "2be8a207",
"metadata": {},
"source": [
"If you don’t get good results, you can always switch back to the `brentq` root finder, since\n",
"the fixed point of a function $ f $ is the root of $ g(x) := x - f(x) $."
]
},
{
"cell_type": "markdown",
"id": "2436ff3a",
"metadata": {},
"source": [
"## Optimization\n",
"\n",
"\n",
"\n",
"Most numerical packages provide only functions for *minimization*.\n",
"\n",
"Maximization can be performed by recalling that the maximizer of a function $ f $ on domain $ D $ is\n",
"the minimizer of $ -f $ on $ D $.\n",
"\n",
"Minimization is closely related to root-finding: For smooth functions, interior optima correspond to roots of the first derivative.\n",
"\n",
"The speed/robustness trade-off described above is present with numerical optimization too.\n",
"\n",
"Unless you have some prior information you can exploit, it’s usually best to use hybrid methods.\n",
"\n",
"For constrained, univariate (i.e., scalar) minimization, a good hybrid option is `fminbound`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a7d33f6f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.optimize import fminbound\n",
"\n",
"fminbound(lambda x: x**2, -1, 2) # Search in [-1, 2]"
]
},
{
"cell_type": "markdown",
"id": "eaeca05c",
"metadata": {},
"source": [
"### Multivariate Optimization\n",
"\n",
"\n",
"\n",
"Multivariate local optimizers include `minimize`, `fmin`, `fmin_powell`, `fmin_cg`, `fmin_bfgs`, and `fmin_ncg`.\n",
"\n",
"Constrained multivariate local optimizers include `fmin_l_bfgs_b`, `fmin_tnc`, `fmin_cobyla`.\n",
"\n",
"See the [documentation](http://docs.scipy.org/doc/scipy/reference/optimize.html) for details."
]
},
{
"cell_type": "markdown",
"id": "ac981bb3",
"metadata": {},
"source": [
"## Integration\n",
"\n",
"\n",
"\n",
"Most numerical integration methods work by computing the integral of an approximating polynomial.\n",
"\n",
"The resulting error depends on how well the polynomial fits the integrand, which in turn depends on how “regular” the integrand is.\n",
"\n",
"In SciPy, the relevant module for numerical integration is `scipy.integrate`.\n",
"\n",
"A good default for univariate integration is `quad`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "097ce8e0",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.integrate import quad\n",
"\n",
"integral, error = quad(lambda x: x**2, 0, 1)\n",
"integral"
]
},
{
"cell_type": "markdown",
"id": "1b89d93b",
"metadata": {},
"source": [
"In fact, `quad` is an interface to a very standard numerical integration routine in the Fortran library QUADPACK.\n",
"\n",
"It uses [Clenshaw-Curtis quadrature](https://en.wikipedia.org/wiki/Clenshaw-Curtis_quadrature), based on expansion in terms of Chebychev polynomials.\n",
"\n",
"There are other options for univariate integration—a useful one is `fixed_quad`, which is fast and hence works well inside `for` loops.\n",
"\n",
"There are also functions for multivariate integration.\n",
"\n",
"See the [documentation](http://docs.scipy.org/doc/scipy/reference/integrate.html) for more details."
]
},
{
"cell_type": "markdown",
"id": "4ec68f01",
"metadata": {},
"source": [
"## Linear Algebra\n",
"\n",
"\n",
"\n",
"We saw that NumPy provides a module for linear algebra called `linalg`.\n",
"\n",
"SciPy also provides a module for linear algebra with the same name.\n",
"\n",
"The latter is not an exact superset of the former, but overall it has more functionality.\n",
"\n",
"We leave you to investigate the [set of available routines](http://docs.scipy.org/doc/scipy/reference/linalg.html)."
]
},
{
"cell_type": "markdown",
"id": "4ca16907",
"metadata": {},
"source": [
"## Exercises\n",
"\n",
"The first few exercises concern pricing a European call option under the\n",
"assumption of risk neutrality. The price satisfies\n",
"\n",
"$$\n",
"P = \\beta^n \\mathbb E \\max\\{ S_n - K, 0 \\}\n",
"$$\n",
"\n",
"where\n",
"\n",
"1. $ \\beta $ is a discount factor, \n",
"1. $ n $ is the expiry date, \n",
"1. $ K $ is the strike price and \n",
"1. $ \\{S_t\\} $ is the price of the underlying asset at each time $ t $. \n",
"\n",
"\n",
"For example, if the call option is to buy stock in Amazon at strike price $ K $, the owner has the right (but not the obligation) to buy 1 share in Amazon at price $ K $ after $ n $ days.\n",
"\n",
"The payoff is therefore $ \\max\\{S_n - K, 0\\} $\n",
"\n",
"The price is the expectation of the payoff, discounted to current value."
]
},
{
"cell_type": "markdown",
"id": "60f2f78c",
"metadata": {},
"source": [
"## Exercise 13.1\n",
"\n",
"Suppose that $ S_n $ has the [log-normal](https://en.wikipedia.org/wiki/Log-normal_distribution) distribution with parameters $ \\mu $ and $ \\sigma $. Let $ f $ denote the density of this distribution. Then\n",
"\n",
"$$\n",
"P = \\beta^n \\int_0^\\infty \\max\\{x - K, 0\\} f(x) dx\n",
"$$\n",
"\n",
"Plot the function\n",
"\n",
"$$\n",
"g(x) = \\beta^n \\max\\{x - K, 0\\} f(x)\n",
"$$\n",
"\n",
"over the interval $ [0, 400] $ when `μ, σ, β, n, K = 4, 0.25, 0.99, 10, 40`.\n",
"\n",
"From `scipy.stats` you can import `lognorm` and then use `lognorm(x, σ, scale=np.exp(μ)` to get the density $ f $."
]
},
{
"cell_type": "markdown",
"id": "1be56031",
"metadata": {},
"source": [
"## Solution to[ Exercise 13.1](https://python-programming.quantecon.org/#sp_ex01)\n",
"\n",
"Here’s one possible solution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8042293d",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"from scipy.integrate import quad\n",
"from scipy.stats import lognorm\n",
"\n",
"μ, σ, β, n, K = 4, 0.25, 0.99, 10, 40\n",
"\n",
"def g(x):\n",
" return β**n * np.maximum(x - K, 0) * lognorm.pdf(x, σ, scale=np.exp(μ))\n",
"\n",
"x_grid = np.linspace(0, 400, 1000)\n",
"y_grid = g(x_grid) \n",
"\n",
"fig, ax = plt.subplots()\n",
"ax.plot(x_grid, y_grid, label=\"$g$\")\n",
"ax.legend()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "ea0b5113",
"metadata": {},
"source": [
"## Exercise 13.2\n",
"\n",
"In order to get the option price, compute the integral of this function numerically using `quad` from `scipy.optimize`."
]
},
{
"cell_type": "markdown",
"id": "204b1e80",
"metadata": {},
"source": [
"## Solution to[ Exercise 13.2](https://python-programming.quantecon.org/#sp_ex02)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e82bee94",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"P, error = quad(g, 0, 1_000)\n",
"print(f\"The numerical integration based option price is {P:.3f}\")"
]
},
{
"cell_type": "markdown",
"id": "8704a443",
"metadata": {},
"source": [
"## Exercise 13.3\n",
"\n",
"Try to get a similar result using Monte Carlo to compute the expectation term in the option price, rather than `quad`.\n",
"\n",
"In particular, use the fact that if $ S_n^1, \\ldots, S_n^M $ are independent\n",
"draws from the lognormal distribution specified above, then, by the law of\n",
"large numbers,\n",
"\n",
"$$\n",
"\\mathbb E \\max\\{ S_n - K, 0 \\} \n",
" \\approx\n",
" \\frac{1}{M} \\sum_{m=1}^M \\max \\{S_n^m - K, 0 \\}\n",
"$$\n",
"\n",
"Set `M = 10_000_000`"
]
},
{
"cell_type": "markdown",
"id": "a039f601",
"metadata": {},
"source": [
"## Solution to[ Exercise 13.3](https://python-programming.quantecon.org/#sp_ex03)\n",
"\n",
"Here is one solution:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7d4514f",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"M = 10_000_000\n",
"S = np.exp(μ + σ * np.random.randn(M))\n",
"return_draws = np.maximum(S - K, 0)\n",
"P = β**n * np.mean(return_draws) \n",
"print(f\"The Monte Carlo option price is {P:3f}\")"
]
},
{
"cell_type": "markdown",
"id": "68390194",
"metadata": {},
"source": [
"## Exercise 13.4\n",
"\n",
"In [this lecture](https://python-programming.quantecon.org/functions.html#functions), we discussed the concept of [recursive function calls](https://python-programming.quantecon.org/functions.html#recursive-functions).\n",
"\n",
"Try to write a recursive implementation of the homemade bisection function [described above](#bisect-func).\n",
"\n",
"Test it on the function [(13.2)](#equation-root-f)."
]
},
{
"cell_type": "markdown",
"id": "d5f902fc",
"metadata": {},
"source": [
"## Solution to[ Exercise 13.4](https://python-programming.quantecon.org/#sp_ex1)\n",
"\n",
"Here’s a reasonable solution:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d96e3402",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"def bisect(f, a, b, tol=10e-5):\n",
" \"\"\"\n",
" Implements the bisection root-finding algorithm, assuming that f is a\n",
" real-valued function on [a, b] satisfying f(a) < 0 < f(b).\n",
" \"\"\"\n",
" lower, upper = a, b\n",
" if upper - lower < tol:\n",
" return 0.5 * (upper + lower)\n",
" else:\n",
" middle = 0.5 * (upper + lower)\n",
" print(f'Current mid point = {middle}')\n",
" if f(middle) > 0: # Implies root is between lower and middle\n",
" return bisect(f, lower, middle)\n",
" else: # Implies root is between middle and upper\n",
" return bisect(f, middle, upper)"
]
},
{
"cell_type": "markdown",
"id": "39c34fc1",
"metadata": {},
"source": [
"We can test it as follows"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a325e2f9",
"metadata": {
"hide-output": false
},
"outputs": [],
"source": [
"f = lambda x: np.sin(4 * (x - 0.25)) + x + x**20 - 1\n",
"bisect(f, 0, 1)"
]
}
],
"metadata": {
"date": 1710455932.8719628,
"filename": "scipy.md",
"kernelspec": {
"display_name": "Python",
"language": "python3",
"name": "python3"
},
"title": "SciPy"
},
"nbformat": 4,
"nbformat_minor": 5
}