Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Zero curve code

I've been meaning to port over a bit of my R code to Python for quite some time. Finally I mustered up the strength to do so and thought I'd share the first bit with you guys as this comes in handy from time to time.

What the code does is download select interest rates (H15) from the fed then bootstraps and interpolates the zero curve with the data. My primary use for this is selecting the appropriate discount factor for options pricing given a current date and tenor. There are plenty of other uses for it though.

code:

#!/usr/bin/env python

import numpy as np  
from scipy import interpolate  
from pandas import *  
from datetime import *

class zeroCurve:

    def __init__(self):  
        self.zero_curve = None  
        self.__h15Url = 'http://www.federalreserve.gov/datadownload/Output.aspx?rel=H15&series=bf17364827e38702b42a58cf8eaa3f78&lastObs=&from=&to=&filetype=csv&label=include&layout=seriescolumn&type=package'

        self.__tenors = [1, 3, 6, 12, 24, 36, 60, 84, 120, 240, 360]  
        self.__cash_names = ["1m", "3m", "6m", "12m"]  
        self.__cash_tenors = [1, 3, 6, 12]  
        self.__bond_names = ["24m", "36m", "60m", "84m", "120m", "240m", "360m"]  
        self.__bond_tenors = [24, 36, 60, 84, 120, 240, 360]  
        self.__zero_times = map(lambda x: x/12.0, [1, 3]+[i for i in xrange(6, 361, 6)])  
        self.__start_date = datetime(2002, 1, 1)

    def load_curve(self):  
        df = read_csv(self.__h15Url, skiprows=70)  
        df.columns = ['date'] + [str(tenor)+"m" for tenor in self.__tenors]  
        df['date'] = map(lambda x: datetime.strptime(x, '%Y-%m-%d'), df['date'])  
        df = df.set_index("date")  
        filter_keep = list(df['1m']!='ND')  
        df = df[filter_keep][self.__start_date:]  
        t360_nD = list(df["360m"]=='ND')  
        df['360m'][t360_nD] = df['240m'][t360_nD]  
        df = df.astype(float)/100  
        return df

    def get_zeros(self, cash_names, bond_names, cash_rates, bond_rates, cash_tenors, bond_tenors):  
        df_all = 1/(cash_rates*cash_tenors/12+1)  
        md = np.append(df_all[['6m', '12m']].values, np.arange(18,361,6))  
        dv = md.cumsum() / 2  
        fe_bonds = np.array([2*(1-md[i-1])/md[0:i].sum() for i in xrange(1,3)])  
        bond_times = np.arange(6, 361, 6)  
        by = interpolate.interp1d(np.append([6,12], bond_tenors),np.append(fe_bonds, bond_rates), kind=1)(bond_times)  
        for i in xrange(2, len(by)):  
            md[i] = (1 - dv[i-1] * by[i])/(1 + by[i] * dv[i-1])  
            dv[i] = md[0:(i+1)].sum() / 2  
        all_times = np.append(cash_tenors,bond_times[2:])  
        all_factr = np.append(df_all.values, md[2:])  
        return -12*np.log(all_factr)/all_times

    def strip_all(self, df):  
        zeros = map(lambda x: (x[0], self.get_zeros(self.__cash_names, self.__bond_names, x[1][self.__cash_names], x[1][self.__bond_names], self.__cash_tenors, self.__bond_tenors)), df.iterrows())  
        df = DataFrame.from_items(zeros, orient="index", columns=["USD"+str(j) for j in self.__cash_tenors+[i for i in xrange(18,361,6)]])  
        return df

    def zero_data(self):  
        df = self.load_curve()  
        self.zero_curve = self.strip_all(df)

    def zero_rate(self, asof, tenor):  
        zero_rates = self.zero_curve[self.zero_curve.index < asof][-1:]  
        return float(interpolate.interp1d(x=np.array(self.__zero_times), y=zero_rates.values[0], kind="linear")(tenor))  

zeroCurve.py

output example:

In [2]: zC = zeroCurve()

In [3]: zC.zero_data()

In [4]: zC.zero_rate(datetime(2014,1,27), 21.0/252.0)  
Out[4]: 9.999958333595127e-05  

If anyone would like to move it into quantopian's platform or speed my code up a bit :-), that would be very nice.

3 responses

You can do most of load_curve method using pandas read_csv:

pd.read_csv(self.__h15Url, skiprows=70, header=None, index_col=0,  
           names=['date'] + [str(tenor)+"m" for tenor in self.__tenors],  
           parse_dates=True, na_values='ND').dropna(how='all').ix['2002-1-1':] / 100.  

Also, it's not best practice to use
from X import * unless you're absolutely sure that you will use each and every thing in that module. It's better to do
from X import Y or in the case of pandas
import pandas as pd

I will take a look at moving this code to the Quantopian platform.

There are a few changes that need to be made to the algorithm to port it to the Quantopian. For security reasons, from (libary) import * is not legal in Quantopian. You will need to specify the specific methods you would like to use. read_csv is not legal in Quantopian. You will need to load the data from the Fed using fetch_csv, which parses the CSV into a pandas data frame using pandas.io.parsers.read_csv.