Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c) 2014 Tingyu Wang.

Black-Scholes Equation

In the class, we discussed how to use numerical methods to solve wave propagation problems and heat equation, which both represent a physical phenomenon. In fact, this powerful tool is not only widely applied by engineers and physicists, but also by traders in a stock market and economists working for the government. Economists would build their own financial models in PDE to estimate future economic growth for each year, and PDE solvers can give traders a sketch about the price of a stock or an option.

Black-Scholes equation is one of the most important models concerning the option pricing. It was first published by Fischer Black and Myron Scholes in 1973. I first read this equation through a book called "17 equations that changed the world".

What is Black-Scholes Equation?

Before we introduce the equation, let us start with some important terminologies behind the equation. Options are used on markets and exchanges. From the definition in Wikipedia, an option is a contract which gives the buyer (the owner) the right, but not the obligation, to buy or sell an underlying asset at a specified price on or before a specified date. We call the price determined in the contract "exercise price (or strike price) ($E$), and call the date "the expiry time ($T$)". The underlying asset has its own value, and we denote the current value of underlying asset as ($S$). If the asset is a car, then $S$ may be equal to the present value of the car, say 20,000 dollars. If the asset is a stock, then $S$ is equal to the current price of the stock (find it in Nasdaq, Dow or S&P).

From the definition, we know that the owner of the option can buy or sell some amount of assets on or before the expiry time. We call it an European option if the option can only be executed on the expiry time ($t_{exercise} = T$). If it is an American option, than means it can be exercised at any time before the expiry time($t_{exercise} \leq T$).

We can understand the option as a right or a priviledge, so the option itself should also have a price, "the price of an option ($V$)", which depends on the current value of underlying asset ($S$) and time ($t$): $V = V(S,t)$. Notice that there is a "buy or sell" in the definition of option above. If the owner of the option has the right to buy some shares of Apple's stock at 50 dollars per share from the buyer before Feb 1, 2015, then this type of option is called call option. We use $C(S,t)$ instead of $V(S,t)$. If the owner of option has the right to "sell", then this type of option is called put option. We denote it as $P(S,t)$ instead of $V(S,t)$. Click the short video below for real-world examples of call options and put options.


In [44]:
from IPython.display import YouTubeVideo
YouTubeVideo('EfmTWu2yn5Q')


Out[44]:

Finally, we introduce the Black-Scholes equation as follows.

$$\begin{equation} \frac{\partial V}{\partial t}(S, t) + \frac{1}{2} \sigma^2 S^2 \frac{\partial^2 V}{\partial S^2}(S, t) + rS \frac{\partial V}{\partial S}(S, t) - rV(S, t) = 0 \end{equation}$$

$V$ stands for the price of the option, $S$ denotes current value of underlying assets, $t$ is time, $r$ denotes the no-risk interest rate (ex.saving rate in bank), and $\sigma$ denotes the volatility of underlying asset (ex.the standard deviation of the stock's returns).

As you can see, the Black-Scholes equation is aimed at calculating the price of an option, so it is the most popular option-pricing method. The only unknown here is the price of option ($V$), depending on $S$ and $t$. In the following part, we will use finite difference methods (both explicit and implicit schemes) to solve this nonlinear second-order parabolic equation.

Initial Conditions

Now, consider we are holding an European, call option $C(S,t)$ of a stock and we are at the expiry time: $t=T$.

  • If the current price of the stock ($S$) > exercise price ($E$), then we definitely want to execute our right to buy the stock at the price $E$, and the profit we gain from the option is $S-E$.

  • If the current price of the stock ($S$) < exercise price ($E$), then there is no need to exercise the call option, therefore, in this case the value of this option is zero.

Thus, for a call option,

$$C(S,T) = max(S-E,0)$$

This is the final condition for the Black-Scholes equation, however, usually we prefer to deal with an inital condition with a form of $C(S,0) = f(S)$. So here we apply change of variables:

$$ t^*(t) = T - t$$

then,

$$\frac{\partial V(S,t)}{\partial t} = - \frac{\partial V(S,t^*)}{\partial t^*}$$

substitute in the Black-Scholes equation:

$$-\frac{\partial V}{\partial t^*}(S, t^*) + \frac{1}{2} \sigma^2 S^2 \frac{\partial^2 V}{\partial S^2}(S, t^*) + rS \frac{\partial V}{\partial S}(S, t^*) - rV(S, t^*) = 0 $$

changing the sign, and use $C$ instead of $V$ since it is a call option:

$$\frac{\partial C}{\partial t^*}(S, t^*) - \frac{1}{2} \sigma^2 S^2 \frac{\partial^2 C}{\partial S^2}(S, t^*) - rS \frac{\partial C}{\partial S}(S, t^*) + rC(S, t^*) = 0 $$

with an initial condition: $C(S,0) = max(S-E,0)$. Be careful that this initial condition reflects the expiry moment $t = T$, and when we are stepping forward to calculate the last time step in $t^*$, we are actually calculating the price of the call option at $t =0$ (that is when we are buying an option).

Boundary Conditions

In order to make it a well-posed partial differential equation, we need to define the boundary conditions on both sides. The current price of the stock $S$ can take value in the interval $[0,\infty)$, which is unbounded.

  • left boundary: $S=0$, then $C(0,t^*) = 0$
  • right boundary: $S\rightarrow \infty$, then $C(S,t^*) = (S-E) $

However, in the numerical method, we need to set an artificial bound $S_{max}$ concerning the space discretization, otherwise we will have inifinity number of grids. Based on Dura's work [ref 1], it is recommended to set $S_{max}$ around four times the exercise price $E$. Here we assume that

$$S_{max} = 4E $$

thus, the boundary conditions can be written as follows:

$$\begin{cases} C(0,t^*) = 0\\ C(S_{max},t^*) = S_{max} - E\\ \end{cases}$$
Problem Statement

Now let's solve a practical problem using our numerical skills!

Consider an European call style option is made for a stock currently trading at 20 dollar per share with volatility 0.4. The term is 3 months (0.25 year) and the strike price is 10 dollar. The prevailing no-risk interest rate is 10 %. What should the price per share be for the option?


In [45]:
%matplotlib inline
import numpy
import matplotlib.pyplot as pyplot
from matplotlib import rcParams
rcParams['figure.dpi'] = 100
rcParams['font.size'] = 16
rcParams['font.family'] = 'StixGeneral'

T = 0.25       # expiry time
r = 0.1        # no-risk interest rate
sigma = 0.4    # volatility of underlying asset
E = 10.        # exercise price
S_max = 4*E    # upper bound of price of the stock (4*E)
Explicit Scheme

For the explicit method, we use a forward difference scheme in time $t$, and a central difference scheme in the current price of underlying asset $S$. The truncation error here is second-order in space and first-order in time $O(\Delta t, \Delta S^2)$. Write out discretized form for each term:

$$\begin{equation} \frac{\partial C}{\partial t^*} = \frac{C_i^{n+1} - C_i^{n}}{\Delta t}\\ \frac{\partial C}{\partial S} = \frac{C_{i+1}^{n} - C_{i-1}^{n}}{2\Delta S}\\ \frac{\partial^2 C}{\partial S^2} = \frac{C_{i+1}^{n} - 2C_i^{n} + C_{i-1}^{n}}{(\Delta S)^2}\\ \end{equation}$$

the discretized Black-Scholes equation: $$\frac{C_i^{n+1} - C_i^{n}}{\Delta t} - \frac{1}{2} \sigma^2 S^2 \frac{C_{i+1}^{n} - 2C_i^{n} + C_{i-1}^{n}}{(\Delta S)^2} - rS \frac{C_{i+1}^{n} - C_{i-1}^{n}}{2\Delta S} + rC_i^{n} = 0 $$

since $S=i \Delta S$, we can rearrange the above equation as:

$$\frac{C_i^{n+1} - C_i^{n}}{\Delta t} - \frac{1}{2} \sigma^2 i^2 (C_{i+1}^{n} - 2C_i^{n} + C_{i-1}^{n}) - \frac{1}{2} ri (C_{i+1}^{n} - C_{i-1}^{n}) + rC_i^{n} = 0 $$

the option price at the next time step can be expressed as: $$\begin{equation} C_i^{n+1} = \frac{1}{2}(\sigma^2 i^2 \Delta t-ri\Delta t)C_{i-1}^n + (1 - \sigma^2 i^2 \Delta t -r \Delta t) C_i^n + \frac{1}{2}(\sigma^2 i^2 \Delta t+ri\Delta t)C_{i+1}^n \end{equation}$$


In [46]:
def FTCS(C, N, M, dt, r, sigma):
    """using forward-time central-space scheme to solve the Black-Scholes equation for the call option price
    
    Arguments:
        C:       array of the price of call option
        N:       total number of time steps
        M:       total number of spatials grids
        dt:      time step
        r:       no-risk interest rate
        sigma:   volatility of the stock
    
    Returns:
        C:       array of the price of call option
    """
    index = numpy.arange(1,M)
    for n in range(N):

        C[1:-1] = 0.5 * (sigma**2 * index**2 * dt - r*index*dt) * C[0:-2] \
             +       (1 - sigma**2* index**2 *dt - r*dt) * C[1:-1]   \
             + 0.5 * (sigma**2 * index**2 * dt + r*index*dt) * C[2:]
    return C

Keep in mind that we are using an explicit scheme, so start with a small time step to satisfy the stability requirement.


In [47]:
N = 2000       # number of time steps 
M = 200        # number of space grids
dt = T/N       # time step
s = numpy.linspace(0, S_max, M+1)   # spatial grid (stock's price)

# initial condition & boundary condition
C = s - E
C = numpy.clip(C, 0, S_max-E)

Run the code and calculate the price of the call option when the stock is currently trading at 20 dollars. (See problem statement)


In [48]:
C_exp = FTCS(C, N, M, dt, r, sigma)
print 'the price of the call option should be around {}, \
if the current price of stock is 20 dollar.'.format(C_exp[int(M/2)])


the price of the call option should be around 10.2470001259, if the current price of stock is 20 dollar.

Let us plot the call option price against the current stock price based on our result.


In [49]:
pyplot.figure(figsize=(8,5), dpi=100)
pyplot.plot(s,C_exp,color='#20b2aa', ls='--', lw=3, label='FTCS');
pyplot.xlabel('Current Price of the Stock (S)')
pyplot.ylabel('Price of the call option (C)')
pyplot.legend(loc='upper left',prop={'size':15});


Implicit Scheme

As we learned in class that the explicit schemes contraint the size of time step, and they will become unstable if we use a larger $\Delta t$. That is why we want to solve it in an implicit way that is unconditionally stable. However, the fully implicit scheme only gives us the fisrt-order accurary in time. Remember in the fourth module, we introduced the Crank-Nicolson scheme to solve the heat equation. The Black-Scholes equation and heat equation are both parabolic equations. So why not try to apply Crank-Nicolson scheme in order to achieve a better accurary?

$$\frac{\partial C}{\partial t^*}(S, t^*) - \frac{1}{2} \sigma^2 S^2 \frac{\partial^2 C}{\partial S^2}(S, t^*) - rS \frac{\partial C}{\partial S}(S, t^*) + rC(S, t^*) = 0 $$

Recall that Crank-Nicolson method is an average of implicit scheme and explicit scheme. Based on the discretized form of Black-Scholes equation, we rewrite each partial derivative in space as an average of the schemes in time step $n$ and $n+1$.

$$\frac{C_i^{n+1} - C_i^{n}}{\Delta t} - \frac{1}{4} \sigma^2 S^2 (\frac{C_{i+1}^{n+1} - 2C_i^{n+1} + C_{i-1}^{n+1}}{(\Delta S)^2} + \frac{C_{i+1}^{n} - 2C_i^{n} + C_{i-1}^{n}}{(\Delta S)^2}) -\\ \frac{1}{2} rS (\frac{C_{i+1}^{n+1} - C_{i-1}^{n+1}}{2\Delta S} + \frac{C_{i+1}^{n} - C_{i-1}^{n}}{2\Delta S}) + \frac{1}{2} r (C_i^{n+1}+C_i^{n} )= 0 $$

replace $S$ with $i\Delta S$, and rearrange terms so that all the unknowns (values in $n+1$ time step) are on the LHS:

$$\frac{\Delta t}{4} (ri-\sigma^2i^2) C_{i-1}^{n+1} + (1+\frac{\Delta t}{2}(r+\sigma^2i^2))C_i^{n+1} + (-\frac{\Delta t}{4}(ri+\sigma^2i^2))C_{i+1}^{n+1}\\= \frac{\Delta t}{4} (\sigma^2i^2-ri) C_{i-1}^{n} + (1-\frac{\Delta t}{2}(r+\sigma^2i^2))C_i^{n} + (\frac{\Delta t}{4}(ri+\sigma^2i^2))C_{i+1}^{n}$$

for notation convenience, we define:

$$\alpha_i = \frac{\Delta t}{4} (ri-\sigma^2i^2) \\ \beta_i = \frac{\Delta t}{2}(r+\sigma^2i^2)\\ \gamma_i = -\frac{\Delta t}{4}(ri+\sigma^2i^2)$$

for $i = 1,2,3, ..., M-1$

so we can simplify the discretized equation as follows:

$$\alpha_i C_{i-1}^{n+1} + (1+\beta_i)C_i^{n+1} + \gamma_i C_{i+1}^{n+1} = -\alpha_i C_{i-1}^{n} + (1-\beta_i)C_i^{n} - \gamma_iC_{i+1}^{n}$$

This linear system of equations can be written as:

$$ AC^{n+1} = A_*C^{n} + B $$

A is the LHS coefficient matrix: \begin{align}\left[ \begin{array}{cccccc} \left(1+\beta_1 \right) & \gamma_1 & 0 & \cdots & 0 \\ \alpha_2 & \left(1+\beta_2 \right) & \gamma_2 & & \vdots \\ 0 & \alpha_3 & \ddots& \ddots & 0 \\ \vdots & & \ddots & \left(1 + \beta_{M-2}\right) & \gamma_{M-2} \\ 0 & \cdots & 0 & \alpha_{M-1} & \left(1 + \beta_{M-1}\right) \end{array} \right] \end{align}


In [50]:
def LHS_matrix(M, alpha, beta, gamma):
    """generate and return the LHS coefficient matrix A.
    
    Arguments:
        M:       total number of spatials grids
        alpha:   array of coefficients on lower diagnoal
        beta:    array of coefficients on diagnoal
        gamma:   array of coefficients on upper diagnoal
    
    Returns:
        A:       LHS coefficient matrix
    """
    # diagonal
    d = numpy.diag(1+beta)
    # upper diagonal
    ud = numpy.diag(gamma[:-1], 1)
    # lower diagonal
    ld = numpy.diag(alpha[1:], -1)
    
    A = d +ud +ld
    return A

At the RHS of the equation, $A_*$ is the coefficient matrix of $C^{n}$ (the call option price array at the previous time step $n$). From above we know matrix $A_*$ is very similar to matrix $A$:

\begin{align}\left[ \begin{array}{cccccc} \left(1-\beta_1 \right) & -\gamma_1 & 0 & \cdots & 0 \\ -\alpha_2 & \left(1-\beta_2 \right) & -\gamma_2 & & \vdots \\ 0 & -\alpha_3 & \ddots& \ddots & 0 \\ \vdots & & \ddots & \left(1 - \beta_{M-2}\right) & -\gamma_{M-2} \\ 0 & \cdots & 0 & -\alpha_{M-1} & \left(1 - \beta_{M-1}\right) \end{array} \right] \end{align}

Vector $B$ is the boundary condition vector based on the Dirichlet boundary condition: $C(0,t) = 0, C(S_{max},t) = S_{max} -E$. Consider the total number of spatial grids $M = 200$ (the 201 grid points' indices are from 0 to 200), then the last equation in the linear system should be:

$$\alpha_{199}C_{198}^{n+1} + (1+\beta_{199})C_{199}^{n+1} + \gamma_{199}C_{200}^{n+1} \\ = -\alpha_{199}C_{198}^{n} + (1-\beta_{199})C_{199}^{n} - \gamma_{199}C_{200}^{n}$$

and we know from the BC that $C_{200}^{n} = C_{200}^{n+1} = (S_{max}-E)$, so rearrange the equation as: $$\alpha_{199}C_{198}^{n+1} + (1+\beta_{199})C_{199}^{n+1} \\ = -\alpha_{199}C_{198}^{n} + (1-\beta_{199})C_{199}^{n} - 2\cdot \gamma_{199}(S_{max}-E)$$

and since the BC at the lower bound is $C(0,t) = 0$, we don't need add any term in the first equation. Thus, the boundary condition vector $B$ should look like:

\begin{align}\left[ \begin{array}{cccccc} 0 \\ \vdots \\ 0 \\ -2\gamma_{199}(S_{max}-E) \end{array} \right] \end{align}


In [51]:
def RHS(C, alpha, beta, gamma, S_max, E):
    """generate and return the RHS vector b.
    
    Arguments:
        C:       array of the price of call option at previous time step
        alpha:   array of coefficients on lower diagnoal
        beta:    array of coefficients on diagnoal
        gamma:   array of coefficients on upper diagnoal
        S_max:   upper bound of stock price
        E:       exercise price
    
    Returns:
        b:       RHS vector
    """
    # diagonal of A_star
    d = numpy.diag(1-beta)
    # upper diagonal of A_star
    ud = numpy.diag(-gamma[:-1], 1)
    # lower diagonal of A_star
    ld = numpy.diag(-alpha[1:], -1)
    
    A_star = d + ud + ld
    b = numpy.dot(A_star,C[1:-1])
    # add BC for the right bound (the last element)
    b[-1] += -2*gamma[-1] * (S_max-E) 
    
    return b

Knowing the value of matrix $A$ and vector $b$, we can solve the linear system using the numpy.linalg.solve function.


In [52]:
def CrankNicolson(C, A, N, alpha, beta, gamma, S_max, E):
    """using Crank-Nicolson scheme to solve the Black-Scholes equation for the call option price.
    
    Arguments:
        C:       array of the price of call option
        A:       LHS coefficient matrix
        N:       total number of time steps       
        alpha:   array of coefficients on lower diagnoal
        beta:    array of coefficients on diagnoal
        gamma:   array of coefficients on upper diagnoal
        S_max:   upper bound of stock price
        E:       exercise price
    
    Returns:
        C:       array of the price of call option
    """
    for t in range(N):
        b = RHS(C, alpha, beta, gamma, S_max, E)
        # use numpy.linalg.solve
        C[1:-1] = solve(A,b)
    return C

We first set the initial condition and boundary condition, and calculate the $\alpha$, $\beta$, $\gamma$ coefficient arrays. Since the Crank-Nicolson scheme is unconditionally stable, we can choose a larger value for time step $dt$.


In [53]:
from scipy.linalg import solve
N = 200        # number of time steps
dt = T/N       # time step

# initial condition & boundary condition
C = s - E
C = numpy.clip(C, 0, S_max-E)

# calculating the coefficient arrays
index = numpy.arange(1,M)

alpha = dt/4 * (r*index - sigma**2*index**2)
beta = dt/2 * (r + sigma**2*index**2)
gamma = -dt/4 * (r*index + sigma**2*index**2)

Given the coefficient arrays $\alpha$, $\beta$, $\gamma$, we can produce the LHS matrix $A$, update the RHS vector $b$ at each time step, and solve the linear system again. Finally we have the solution by Crank-Nicolson scheme.


In [54]:
A = LHS_matrix(M, alpha, beta, gamma)
C_imp = CrankNicolson(C, A, N, alpha, beta, gamma, S_max, E)
print 'the price of the call option should be around {}, \
if the price of stock is 20 dollar.'.format(C_imp[int(M/2)])


the price of the call option should be around 10.2469995107, if the price of stock is 20 dollar.

Plot the result and compare with the solution of explicit scheme (FTCS).


In [55]:
pyplot.figure(figsize=(8,5), dpi=100)
pyplot.plot(s,C_exp,color='#20b2aa', ls='-.', lw=3, label='FTCS');
pyplot.plot(s,C_imp,color='#cd3333', ls='--', lw=3, label='Crank-Nicolson')
pyplot.xlabel('Current Price of the Stock (S)')
pyplot.ylabel('Price of the call option (C)')
pyplot.legend(loc='upper left',prop={'size':15});


These two solutions are very close to each other. Are they both correct? Let us compare them with an exact solution.

An Exact Solution of Black-Scholes Equation

As for an European call option problem, mathematicians provide us with an analytical solution (here is the derivation of the solution):

$$\begin{equation} C(S,t) = SN(d_1) -Ee^{-r(T-t)}N(d_2) \end{equation}$$

where $N(x)$ is the cumulative distribution function for a standardised normal random variable (we use norm.cdf to calculate it in python), given by

$$\begin{equation} N(x) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{x} {e^{-\frac{1}{2}y^2}dy} \end{equation}$$

and

$$\begin{equation}\begin{split} d_1 & = \frac{ln(S/E)+(r + \frac{1}{2}\sigma^2)T}{\sigma \sqrt{(T-t)}}\\ d_2 & = d_1 - \sigma \sqrt{(T-t)} \end{split}\end{equation}$$

Recall that we change variable $t^* = T-t$ when we are dealing with the initial condition, so the analytical solution should be:

$$\begin{equation} C(S,t^*) = SN(d_1) -Ee^{-rT}N(d_2) \end{equation}$$

and

$$\begin{equation}\begin{split} d_1 & = \frac{ln(S/E)+(r + \frac{1}{2}\sigma^2)T}{\sigma \sqrt{t*}}\\ d_2 & = d_1 - \sigma \sqrt{t^*} \end{split}\end{equation}$$

In [56]:
from scipy.stats import norm

C_exact = numpy.zeros(M+1)

d1 = (numpy.log1p(s/E) + (r+0.5*sigma**2)*T) / (sigma * numpy.sqrt(T))
d2 = d1 - (sigma * numpy.sqrt(T))
C_exact = s * norm.cdf(d1) - E*numpy.exp(-r*T) * norm.cdf(d2)
C_exact = numpy.clip(C_exact, 0, numpy.inf)

Don't forget there is still an underlying condition that when $S<E$, we won't exercise the call option. Therefore, the price of call option should be zero rather than a negative value. This explains the if condition inside the loop.

Let plot the exact solution together with our numerical results.


In [57]:
pyplot.figure(figsize=(8,5), dpi=100)
pyplot.plot(s, C_exp, color='#20b2aa', ls='-.', lw=3, label='FTCS');
pyplot.plot(s, C_imp, color='#cd3333', ls='--', lw=3, label='Crank-Nicolson')
pyplot.plot(s, C_exact, color='#FFFF00', ls='-', lw=2, label='Analytical')
pyplot.xlabel('Current Price of the Stock (S)')
pyplot.ylabel('Price of the call option (C)')
pyplot.legend(loc='upper left',prop={'size':15});


Provided the fact that the stock is currently trading at 20 dollars, our pricing strategy of the call option is shown below. The result of Crank-Nicolson method is slightly closer to analytical result.


In [58]:
print 'FTCS:           C(S=20, t=0) = {}'.format(C_exp[int(M/2)])
print 'Crank Nicolson: C(S=20, t=0) = {}'.format(C_imp[int(M/2)])
print 'Exact Solution: C(S=20, t=0) = {}'.format(C_exact[int(M/2)])


FTCS:           C(S=20, t=0) = 10.2470001259
Crank Nicolson: C(S=20, t=0) = 10.2469995107
Exact Solution: C(S=20, t=0) = 10.2469009391

Using the parameters: $r = 0.1$, $\sigma = 0.4$, $E = 10$, we can generate a pricing strategy by changing current the stock price (S) and expiry time (T) as the table below (given by Crank-Nicolson scheme):


In [59]:
import pandas

In [60]:
%%file data.csv
S = 15,S = 20,S = 25
T = 3 months,5.2604,10.2470,15.2459
T = 6 months,5.5637,10.4914,15.4728
T = 9 months,5.8744,10.7354,15.6766


Overwriting data.csv

In [61]:
pandas.read_csv('data.csv')


Out[61]:
S = 15 S = 20 S = 25
T = 3 months 5.2604 10.2470 15.2459
T = 6 months 5.5637 10.4914 15.4728
T = 9 months 5.8744 10.7354 15.6766
Reference
  1. Dura, Gina, and Ana-Maria Moşneagu. "Numerical approximation of Black-Scholes equation." Annals of the Alexandru Ioan Cuza University-Mathematics 56.1 (2010): 39-64.
  2. Stewart, Ian. In Pursuit of the Unknown: 17 Equations That Changed the World Paperback, Basic Books, 2013. Print
  3. "Option (finance)." Wikipedia. Wikimedia Foundation, n.d. Web. 13 Jan. 2015.
  4. Fischer Black and Myron Scholes (1973). The Pricing of Options and Corporate Liabilities, Journal of Political Economy, Vol. 81, No. 3 (May - Jun., 1973), pp. 637-654
  5. Francois Coppex. Solving the Black-Scholes equation: a demystification. 2009. http://www.francoiscoppex.com/blackscholes.pdf
  6. Jiang, L. (2005). Mathematical modeling and methods of option pricing. Singapore: World Scientific.

In [62]:
from IPython.core.display import HTML
css_file = '../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())


Out[62]: