Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save 5amfung/66e0a6cb267eb026dbcaa1c37e719941 to your computer and use it in GitHub Desktop.

Select an option

Save 5amfung/66e0a6cb267eb026dbcaa1c37e719941 to your computer and use it in GitHub Desktop.
Building a Zero Curve with Forward Rate Agreements Using Pandas
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Building a Zero Curve with Forward Rate Agreements Using Pandas\n",
"\n",
"In finance world, if you wanted to price an instrument and figure out the future value at t(n) from t0 (now), you would need to use the spot yield curve. Among the professional traders, the spot yield curve is called zero curve.\n",
"\n",
"If you have a $1000 now to invest, you could easily get the spot rate by going to a bank and ask to deposit you money in a 1 year CD. The CD rate is your benchmark. If you invested into something (assuming the risk is relatively the same) with lesser return than the CD, you know you would be better off putting the money in the CD. This is no brainer.\n",
"\n",
"But what if you know you would have $1000 a year from now and want to invest it for one year? You can't walk into a bank and try to lock in an interest rate that is a year from now. The bank won't and probably can't tell you what the future interest rates are. Maybe different department of the bank can but just not the one you walk in because the target customer are different.\n",
"\n",
"In fact banks do know what the future interest rates are. That is what FRA is.\n",
"\n",
"FRA is an agreement between two parties such that if you lend your money, you would get the specified interest plus principal back at the end of the term.\n",
"\n",
"In this article, we will build a zero curve based on FRAs (Forward Rate Agreement) using Pandas. With this zero curve, you can easily price something anywhere from one day to any number of days up to next ten years.\n",
"\n",
"For simplicity, the FRA we use is a one year term. In reality, the Eurodollar future, which is a FRA, can either be one month or three months long. Please note that all interest rate regardless of its term is ALWAYS quoted as annual rate.\n"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {},
"outputs": [],
"source": [
"import calendar\n",
"import json\n",
"import numpy as np\n",
"import os\n",
"import pandas as pd\n",
"from decimal import Decimal as D\n",
"from io import StringIO\n",
"\n",
"fra = pd.DataFrame({\n",
" 'Description': ['2019', '2020', '2021', '2022', '2023'],\n",
" 'start_date': pd.date_range('2019-01-01', periods=5, freq='YS'),\n",
" 'end_date': pd.date_range('2019-01-01', periods=5, freq='Y'),\n",
" 'rate': [2.2375, 2.4594, 2.6818, 2.7422, 2.6625]\n",
"})\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we have 5 years of FRA as of 2019-01-01. If you deposit the money on 2019-01-01, you would get 2.2375% interest at the end of the first year, 2.4594% the second year, so on and so forth."
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Description</th>\n",
" <th>start_date</th>\n",
" <th>end_date</th>\n",
" <th>rate</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2019</td>\n",
" <td>2019-01-01</td>\n",
" <td>2019-12-31</td>\n",
" <td>2.2375</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2020</td>\n",
" <td>2020-01-01</td>\n",
" <td>2020-12-31</td>\n",
" <td>2.4594</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2021</td>\n",
" <td>2021-01-01</td>\n",
" <td>2021-12-31</td>\n",
" <td>2.6818</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2022</td>\n",
" <td>2022-01-01</td>\n",
" <td>2022-12-31</td>\n",
" <td>2.7422</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2023</td>\n",
" <td>2023-01-01</td>\n",
" <td>2023-12-31</td>\n",
" <td>2.6625</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Description start_date end_date rate\n",
"0 2019 2019-01-01 2019-12-31 2.2375\n",
"1 2020 2020-01-01 2020-12-31 2.4594\n",
"2 2021 2021-01-01 2021-12-31 2.6818\n",
"3 2022 2022-01-01 2022-12-31 2.7422\n",
"4 2023 2023-01-01 2023-12-31 2.6625"
]
},
"execution_count": 77,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fra"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We know the interest rate of each year, but if we wanted to find out the compounded interest rate at the end, I can assure you that the answer is not merely adding the interest rates together. What we do below is that we compound the growth year after year. Let's break down the steps.\n",
"\n",
"Step 1: Calculate the growth for the period from M to N, hence, `mxn_growth`."
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [],
"source": [
"fra['mxn_growth'] = 1 + fra['rate'] / 100"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Step 2: The compounded growth is previous compounded growth multiplied by the growth of this period. Because the growth is compounded since time 0, we call it `0xn_growth`."
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [],
"source": [
"fra['0xn_growth'] = fra['mxn_growth'].cumprod()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Step 3: Each FRA is a one year term but the growth is a compound of previous years."
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [],
"source": [
"fra['years'] = 1\n",
"fra['cummulative_years'] = fra['years'].cumsum()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Step 4: Finally we know the total growth `0xn_growth` for the total number of years `cummulative_years`. We just need to normalize it to annual rate. As we pointed out earlier, interest rate is ALWAYS quoted as annual rate."
]
},
{
"cell_type": "code",
"execution_count": 81,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>Description</th>\n",
" <th>start_date</th>\n",
" <th>end_date</th>\n",
" <th>rate</th>\n",
" <th>mxn_growth</th>\n",
" <th>0xn_growth</th>\n",
" <th>years</th>\n",
" <th>cummulative_years</th>\n",
" <th>zero_rate</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>2019</td>\n",
" <td>2019-01-01</td>\n",
" <td>2019-12-31</td>\n",
" <td>2.2375</td>\n",
" <td>1.022375</td>\n",
" <td>1.022375</td>\n",
" <td>1</td>\n",
" <td>1</td>\n",
" <td>0.022375</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2020</td>\n",
" <td>2020-01-01</td>\n",
" <td>2020-12-31</td>\n",
" <td>2.4594</td>\n",
" <td>1.024594</td>\n",
" <td>1.047519</td>\n",
" <td>1</td>\n",
" <td>2</td>\n",
" <td>0.023484</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>2021</td>\n",
" <td>2021-01-01</td>\n",
" <td>2021-12-31</td>\n",
" <td>2.6818</td>\n",
" <td>1.026818</td>\n",
" <td>1.075612</td>\n",
" <td>1</td>\n",
" <td>3</td>\n",
" <td>0.024594</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>2022</td>\n",
" <td>2022-01-01</td>\n",
" <td>2022-12-31</td>\n",
" <td>2.7422</td>\n",
" <td>1.027422</td>\n",
" <td>1.105107</td>\n",
" <td>1</td>\n",
" <td>4</td>\n",
" <td>0.025300</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>2023</td>\n",
" <td>2023-01-01</td>\n",
" <td>2023-12-31</td>\n",
" <td>2.6625</td>\n",
" <td>1.026625</td>\n",
" <td>1.134531</td>\n",
" <td>1</td>\n",
" <td>5</td>\n",
" <td>0.025565</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" Description start_date end_date rate mxn_growth 0xn_growth years \\\n",
"0 2019 2019-01-01 2019-12-31 2.2375 1.022375 1.022375 1 \n",
"1 2020 2020-01-01 2020-12-31 2.4594 1.024594 1.047519 1 \n",
"2 2021 2021-01-01 2021-12-31 2.6818 1.026818 1.075612 1 \n",
"3 2022 2022-01-01 2022-12-31 2.7422 1.027422 1.105107 1 \n",
"4 2023 2023-01-01 2023-12-31 2.6625 1.026625 1.134531 1 \n",
"\n",
" cummulative_years zero_rate \n",
"0 1 0.022375 \n",
"1 2 0.023484 \n",
"2 3 0.024594 \n",
"3 4 0.025300 \n",
"4 5 0.025565 "
]
},
"execution_count": 81,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fra['zero_rate'] = fra['0xn_growth'] ** (1 / fra['cummulative_years']) - 1\n",
"fra"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There you have it. You want to know what your return is at the end of 2020, 2.3484% is your interest rate.\n",
"\n",
"Wait! You might say, this is too easy.\n",
"\n",
"Yes, indeed, remember we intentionally made each FRA a one year term. What if they are one month or three months like the Eurodollar futures? How would you do it differently? I will leave that challenge to you."
]
}
],
"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.6.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment