The following regression of leverage ratios at quarter $t+1$ on the same at quarter $t$ $$\left(\frac{D}{V}\right)_{t+1} = \gamma \left(\frac{D}{V}\right)^* + (1-\gamma) \left(\frac{D}{V}\right)_{t} + \epsilon_{t+1}$$ should describe reasonably well the behavior of a corporation that partially adjusts its leverage ratio from where it currently is towards a long-term target $\left(\frac{D}{V}\right)^*,$ where $\gamma$ measures the speed of adjustment. For more discussion, see section 4.6 of my notes.
Here $D$ is the value of all non-operating liabilities (see below for details) while $V$ is enterprise value. The current leverage ratio matters because adjusting one's leverage ratio either by issuing more debt or retiring some debt is costly and takes time. White noise $\epsilon$ simply reflects the fact that every one has a plan until they get punched in the mouth (Mike Tyson, 1987.)
Notice that if $\gamma=0$ the equation degenerates to $$\left(\frac{D}{V}\right)_{t+1} = \left(\frac{D}{V}\right)_{t} + \epsilon_{t+1}$$ which is known as a random walk. A corporation whose leverage ratio follows a random walk has no long-term target. Finding evidence of a long-term leverage target, therefore, is rejecting the hypothesis that $\gamma=0$.
Our main goal is to estimate $\gamma$ and $\left(\frac{D}{V}\right)^*.$ For that we'll need a few standard python modules.
import pandas as pd # pandas is excellent for creating and manipulating dataframes, R-style
import numpy as np # great for array manipulations and simulations
import matplotlib.pyplot as plt #graphing module with matlab-like properties
%matplotlib inline
import requests # to make requests to webpages (like mine)
import statsmodels.api as sm # module full of standard statistical and econometric models, including OLS and time-series stuff
Next we import the data from my webpage and look at it a bit.
pd.options.display.float_format = '{:,.2f}'.format # this is the financial format we like
df = pd.read_csv('http://erwan.marginalq.com/index_files/tea_files/IBMlast.csv')
df.describe() # summary stats for our newly imported dataset
df[0:4]
Next we create the variables we need. Notice the use of panda's shift operator to easily create a lagged variable.
df['FF_PFD_STK']=df['FF_PFD_STK'].fillna(0) # note how we make sure that the NaN becomes zeros so as not to lose data
df['V']=df['FF_DEBT_ST']+df['FF_DEBT_LT']+df['FF_PFD_STK']+df['FF_MKT_VAL']-df['FF_CASH_ST'] # this is EV
df['D']=df['FF_DEBT_ST']+df['FF_DEBT_LT']+df['FF_PFD_STK']-df['FF_CASH_ST'] # this is leverage
df['Lev']=df['D']/df['V']
df['LevLag']=df.Lev.shift(1)
Next we plot leverage. Based on this it's obviously hard to tell whether IBM has a long-term tendency to return to some target. There seem to be three regimes: high leverage early on, low leverage in the middle, and high leverage again recently. Clearly, a regime-switching model would be a better way to represent IBM's leverage history. Still, for illustration, we will estimate our model as is.
datenew=np.asarray([1994.125+ i*.25 for i in range(len(df['DATE']))]) # a date variable that makes for a prettier chart
plt.plot(datenew,df['Lev'])
Now we run our regression of leverage on lagged leverage.
y=df.loc[1:,'Lev'] # note that this excludes the first row of data since lag is missing for that line
x=df.loc[1:,'LevLag']
x=sm.add_constant(x) # we run a standard OLS with constant
mod=sm.OLS(y,x)
res=mod.fit()
res.summary()
Now we need to convert those coefficients to the objects we are trying to estimate. Recall our model:$$\left(\frac{D}{V}\right)_{t+1} = \gamma \left(\frac{D}{V}\right)^* + (1-\gamma) \left(\frac{D}{V}\right)_{t} + \epsilon_{t+1}.$$ To get $\gamma$ we just need to do 1 - the coefficient on lag leverage, and then we can back out $\left(\frac{D}{V}\right)^*$ from the fact that the constant is an estimate of $\gamma \left(\frac{D}{V}\right)^*$. The python algebra below gives: $$\left[\gamma,\left(\frac{D}{V}\right)^*\right] \approx [0.084,0.145].$$
gamma=1-res.params[1]
DtoVstar=res.params[0]/gamma
print('gamma estimates to %.3f or so' %gamma)
print('DtoVstar estimates to %.3f or so' %DtoVstar)
Now we perform a DF test for H0: unit root. The default model in the module below is the one we ran, so no need to specify options, it's all baked in. We get a high p-value. We cannot reject the hypothesis that $\gamma=0$ in favor of the hypothesis with $\gamma>0$ with any sort of confidence, which is hardly surprising given the graph above.
from statsmodels.tsa.stattools import adfuller
adf_test = adfuller(df['Lev'])
# print(adf_test[0])
print('The p-value of H0 is %.5f' %adf_test[1])
What if we exclude the latest leverage surge? After restricting the analysis to pre-2017 data, then we can reject the hypothesis that $\gamma=0$ with high confidence. This slicing data until we get the p-value we want is data-mining of the worst kind but this is consistent with what we saw on the chart above, namely the idea that IBM held their leverage in check until 2017 when something clearly changed.
df['DATE'] = pd.to_datetime(df['DATE'])
slice = (df['DATE'] > '1994-1-1') & (df['DATE'] <= '2017-12-31')
adf_test2=adfuller(df['Lev'].loc[slice])
print('The p-value of H0 is now %.5f' %adf_test2[1])