How I use use coding to learn more in-depth in maths and economics
How I write scripts in Python to learn more in-depth in maths and economics and have more fun solving exercises.
# Why is coding a valuable tool in mathematics and economics?
Mathematics and economics are two fields that are often considered abstract and theoretical. However, these fields have a lot of practical applications in the real world. One way to gain a deeper understanding of these subjects is by writing small pieces of code, so-called “scripts.” When you work with code, you are forced to truly understand the underlying concepts and apply them correctly; otherwise, your code will run into errors or outright fail.
Furthermore, coding is often necessary to solve real-world problems that are often complicated in nature and require lots of repetitive calculations. The process of writing scripts helps you develop a deeper understanding of the subject matter and improves your problem-solving skills. In this post, we will explore how coding small pieces of code can help achieve in-depth knowledge in mathematics and economics.
# My adventure into coding
As far back as I can remember, I have always liked learning new things and solving problems efficiently. I really dislike spending unnecessary time on tedious tasks. In the last years of primary school, I attended some after-school coding activities. I quickly realized that I found coding to be very interesting and exciting. I built websites with HTML and CSS, made small games in Python, and tried making mods (small expansion packs) for the game Minecraft.
In the last few years, my focus has primarily been coding for scientific usage, math and economics. Thus, I have explored Python packages such as NumPy, SymPy, SciPy, Pandas, and more.
I am not a financial advisor. The information contained in this website is for general information purposes only and should not be taken as professional advice. All ideas, opinions, code and/or recommendations expressed or implied herein are for informational and educational purposes only and should not be construed as financial product advice or an inducement or instruction to invest, trade, and/or speculate in the markets.
In no event will I be liable for any loss or damage including without limitation, indirect or consequential loss or damage, or any loss or damage whatsoever arising from loss of data or profits arising out of, or in connection with, the use of this website.
# Example
At the time of writing this, I am in my 3rd semester at Southern University of Denmark. Although I still have three semesters left of my bachelor’s degree, I have already taken many interesting courses. In some of these courses, I have tried to automate some of the exercises we have worked with and build Python libraries for the most commonly used computational methods. Read on to see a script from an investment course.
# Options and the binomial model
I have just recently completed a course in financing and investment. In this course, I have been very fond of the topics of options and derivatives. I find it very interesting how one can combine these in specific ways to hedge against risks, etc. I thought it would be a fun little challenge to create a script that can price call and put options with the binomial model.
I will not go into too much detail on the underlying mechanisms of how options work, but there are many great sources online to learn more about options and the binomial model. For our university course, we used the book Financial Markets and Corporate Strategy by David Hillier, Mark Grinblatt, and Sheridan Titman, an excellent book that goes into great detail on many topics including options. I have learnt about the model described in the next section from this book. The model was originally proposed by William Sharpe1 and later on formalised by Cox, Ross and Rubinstein2 and is sometimes referred to as the Cox-Ross-Rubinstein model.
For the following part, I assume that you know the basics of options. I will therefore not explain what an option is or how it works.
# The model
The model has got some simplifying assumptions:
- No arbitrage.
- General assumptions (i.e no transactional fees, the underlying assets are indivisible).
- No payments from the underlying asset (i.e no dividends).
- The options are european (they cannot be exercised prematurely).
Before we get started with the coding, we will introduce the basic notation and principles of the binomial model.
Think of an asset, e.g. a stock. The asset will either increase in price or decrease in price for a given period, say a month. We introduce two terms to quantify this development, \(u\) (up movement) and \(d\) (down movement). Let \(S_0\) be the price of the underlying asset at time \(t=0\). Then \[S_u = S\cdot u.\] is the price of the underlying asset after an up movement at \(t=1\), and \[S_d = S\cdot d.\] is the price of the underlying asset after the down movement \(t=1\). Thus we can illustrate the development in price of the underlying asset,
Now let us take a numerical example, let \(S_0=100, u = 1,15\,\text{and}\, d = 0,85\) then
The asset will either increase by 15% or decrease by 15% after a period. We have illustrated the movement of the underlying asset for a one-period model. But how would we draw a two-period model? You guessed it, we can just add another column of numbers by multiplying \(S_u\) and \(S_d\) with \(u\) or \(d\) again, thus
Note that the tree will only be recombining if \(u\) and \(d\) are constant throughout the tree; otherwise, the tree will not be recombining.
But how do we decide if there is an up movement or a down movement? Let us introduce \(\pi\), the risk-neutral probability of an up movement. \(\pi\) is given by,
\[\pi = \frac{r-d}{u-d} = \frac{(1+r_f)^{\Delta t}-d}{u-d}.\]
Note that whether we use \((1+r_f)^{\Delta t}\) or \(e^{r_f\cdot \Delta t}\) for the interest rate depends on whether interest compounds yearly or continuously. Let us draw the one period tree with the risk-neutral probabilities,
# Pricing options
To price an option, we can go through the tree backwards; that is, we start at the end nodes and determine the payoffs if we exercise the option. We only care to exercise the option if there is a positive payoff; if the payoff is negative, we simply do not exercise it, and the value is 0. For a call option, we exercise it if the value of the underlying asset is higher than the strike price. The payoff for a call option is given by
\[C_\text{payoff} = \max(0, S_t-K).\]
The payoff for a put option is given by
\[P_\text{payoff} = \max(0, K-S_t).\] Where \(S_t\) is the value of the underlying asset at time \(t\) and \(K\) is the strike price.
Assume a european call option with strike \(K=90\) and one period to expiry. Let the risk-free interest rate be 1% with yearly compounds. We can now compute \(\pi\)
\[\pi = \frac{r-d}{u-d} = \frac{(1+0,01)^1-0,85}{1,15-0,85} = 0,5333.\]
Then the price of the call option is
\[\begin{aligned} C(t;T,K) &= \frac{\pi\cdot C_u + (1-\pi)\cdot C_d}{(1+r_f)^{\Delta t}} \\ &= \frac{0,5333\cdot\max(0, 115-90) + (1-0,5333)\cdot \max(0,85-90)}{(1+0,01)^1} \\ &= \frac{0,5333\cdot 25 + (1-0,5333)\cdot 0}{(1+0,01)^1} = 13,2013. \end{aligned}\]Thus, we have determined the price of the call option to be 13,2013. If there were more periods, say n periods, we would have to do this calculation for nodes at \(t=\{0,1,\dots,n-1\}\) and at the end nodes \(t=n\) we compute the payoff, exactly as we have done above for one period.
# Probability-weighted sum of the payoffs
Now that we have a good mathematical foundation, we can start working on the code. I will try to do my best to include comments explaining the code.
We could loop through the whole tree, doing the calculation above for each node; however, that would not be super efficient as we would have to do the same computation many times. I have therefore chosen to opt for an alternative. In essence, when we work our way through the binomial tree, we are computing the probability-weighted sum of the payoffs.
We can therefore skip computing the value for each node in the tree if we can find the probabilities at the end nodes. A simple way to do this is by using the binomial probability mass function,
\[P(X=x) = \begin{pmatrix}n \\ x\end{pmatrix} p^x (1-p)^{n-x}\]Where x denotes a success of n trials, p is the probability of a success (a risk-neutral up movement) and the binomial coefficient is given by
\[\begin{pmatrix}n \\ x\end{pmatrix} = \frac{n!}{x!(n-x)!}.\]Let us plug our notation in
\[P_{i} = \begin{pmatrix}n \\ i\end{pmatrix} \pi^i (1-\pi)^{n-i}\]Where n denotes the periods and i is an index we can loop over. Let us get coding.
# Python code
Please note that the following code is in no way unique. There are many other people online who has implemented the binomial model in different programming languages using this method. John at CodeArmo has written a great article about option pricing with Python where he also uses the same approach. He also goes into more detail on the theory behind the binomial model.
I will also recommend watching QuantPy’s great video where he shows a few different approaches to implement the binomial model. It is very interesting.
Let us get started! I start by importing NumPy which is my go-to library for math in Python.
## I import the NumPy library as np.
import numpy as np
Next, I define the variables that we will need for the function to make the code easier to read. I reuse the information from above.
# Defining variables.
S0 = 100 # Value of underlying asset at t = 0.
K = 90 # Strike price at expiry.
n = 1 # n-period model.
t = 1 # time period.
u = 1.15 # u factor for an up movement.
d = 0.85 # d factor for a down movement.
rf = 0.01 # risk-free interest rate.
Now for the main function. I will show it a few steps at a time.
# Define a function that takes our variables as inputs
def bPriceEuropean(S0, K, n, t, u, d, rf, accrual='cont', type='call'):
dt = t/n # Compute the step size.
# Compute the discount factor and the risk-neutral probability.
# we also add a little logic to handle the interest rate compounding.
discountFactor = np.exp(rf*dt) if accrual == 'cont' else (1+rf)**dt
rfProb = (discountFactor-d)/(u-d)
Finally, we will run through the tree, determining the value of the underlying asset, the payoff at the end nodes, the probabilities and the price of the option.
ans = 0 # set the return variable to 0.
# Determine payoffs and probabilities from top to bottom in the tree.
for i in reversed(range(n+1)):
# Compute the value of the underlying asset at index i.
st = S0*u**i * d**(n-i)
# Compute the value of the payoff depending on if the option
# is of type call or put.
payoff = max(0,st-K) if type =='call' else max(0,K-st)
# Compute the binomial coefficient at index i.
binomCoeff = np.math.factorial(n) / (np.math.factorial(n-i)*np.math.factorial(i))
# Then we use the binomial coefficient to determine the
# probability at the end node.
payoffProb = binomCoeff*rfProb**i*(1-rfProb)**(n-i)
# Before adding the probability-weighted value of the payoff
# we determine the discount factor to discount it to t=0.
discountToStart = np.exp(-rf*t) if accrual == 'cont' else (1+rf)**(-t)
# Finally add the probability-weighted payoff to the return variable
ans +=payoffProb*payoff*discountToStart
# Once all the probability-weighted payoffs have been computed and summed.
# The answer is returned.
return(ans)
And combined, the whole function looks like this:
# Define a function that takes our variables as inputs
def bPriceEuropean(S0, K, n, t, u, d, rf, accrual='cont', type='call'):
dt = t/n # Compute the step size.
# Compute the discount factor and the risk-neutral probability.
# we also add a little logic to handle the interest rate compounding.
discountFactor = np.exp(rf*dt) if accrual == 'cont' else (1+rf)**dt
rfProb = (discountFactor-d)/(u-d)
ans = 0 # set the return variable to 0.
# Determine payoffs and probabilities from top to bottom in the tree.
for i in reversed(range(n+1)):
# Compute the value of the underlying asset at index i.
st = S0*u**i * d**(n-i)
# Compute the value of the payoff depending on if the option
# is of type call or put.
payoff = max(0,st-K) if type =='call' else max(0,K-st)
# Compute the binomial coefficient at index i.
binomCoeff = np.math.factorial(n) / (np.math.factorial(n-i)*np.math.factorial(i))
# Then we use the binomial coefficient to determine the
# probability at the end node.
payoffProb = binomCoeff*rfProb**i*(1-rfProb)**(n-i)
# Before adding the probability-weighted value of the payoff
# we determine the discount factor to discount it to t=0.
discountToStart = np.exp(-rf*t) if accrual == 'cont' else (1+rf)**(-t)
# Finally add the probability-weighted payoff to the return variable
ans +=payoffProb*payoff*discountToStart
# Once all the probability-weighted payoffs have been computed and summed.
# The answer is returned.
return(ans)
Thus, we have coded a little script that can value options for us. If we run the script on the example, the output is as follows:
print(bPriceEuropean(S0, K, n, t, u, d, rf, accrual='yearly', type='call'))
#> 13.201320132013198
Exactly the same answer as we calculated in the section above. Now we can play around with the script and see what happens if we update the variables a bit.
S0 = 80 # Value of underlying asset at t = 0
K = 75 # Strike price at expiry
n = 10 # n-period model
t = 4 # time period
u = 1.35 # u factor for an up movement
d = 0.65 # d factor for a down movement
rf = 0.05 # risk-free interest rate
print(bPriceEuropean(S0, K, n, t, u, d, rf, accrual='cont', type='put'))
#> 21.745522912417176
It is super quick and easy to compute the price of a put option now, right? Calculating a 10-period binomial tree manually would take a while, and we have just done it in a few seconds!
When I first started the little challenge of coding this script, I had not thought about using the probabilities at the end nodes to calculate the possibility-weighted sum of the payoffs to determine the price of the options; however, once I thought through the problem and tried working with it, I realized that it was a possibility. When I turned to Google I found that many option-pricing calculators actually use this method. This is an excellent example of how coding can help improve your intuition and understanding of the underlying concepts of a topic.
# How to get started coding in mathematics and economics
Pick an easy language like Python and start coding!
Truth be told, there are not that many tips and tricks for coding in mathematics and economics. You just have to get started and practice a lot! If you feel that coding is a little overwhelming, which is something I often hear, I recommend starting out small with simple math and trying to make some small scripts just for the challenge of it. Find something that you find interesting and try to code it. As you progress and become better at coding, you will start to see possibilities everywhere you look: “Oh, that looks like a fun process to automate!” At least that has been my experience.
William F. Sharpe – Biographical. NobelPrize.org. Nobel Prize Outreach AB 2024. Mon. 08 Jan 2024. https://www.nobelprize.org/prizes/economic-sciences/1990/sharpe/biographical/ ↩
Cox, J. C., Ross, S. A., Rubinstein, M., Option Pricing: A Simplified Approach, Journal of Financial Economics (1979) ↩