Before I start, I asked this question on Quant Finance before. But other Questions going into the same direction have been redirected to this stack exchange. Therefore I post this here as well.
I'm working with Python and use the statsmodels.tsa.vector_ar.vecm.coint_johansen
function to analyze if several stocks have a stationary error term with respect to pairs trading. I pass the log of the close prices for the stocks into the function and chose constant term
for det_order
(0) and number of lagged differences as 1 for k_ar_diff
(1).
Based on the trace statistics I select stock pairs if the r0_trace
> r0_crit
(5% quantile) and r1_trace
<= r1_crit
(5% quantile). Which is in line with the literature that I have read into that topic. Based on the weights for the pairs the resulting error should then be stationary.
Out of curiosity I then used the ADF and KPSS test on the resulting errors for the training periods, which then resulted in only 30% of the errors being stationary. Now I'm wondering where this issue has its roots, is it rather on the application of the Johansen-Test or based on the ADF/ KPSS-test.
The dataset contains daily close prices and the training period is either 1 year, 6 months, 3 months, or 1 month. The results for the ADF and KPSS tests do not really differ based on the training period duration.
For KPSS and ADF I use the default setting of the statsmodels.tsa.stattools.adfuller/kpss
function.
Below is the code that determines if a pair has a stationary error (spread) for the training period.
# Here I define the Johansen summary
def coint_johansen_summary(data,Quantil=95):
if Quantil == 90:
a = 0
elif Quantil == 99:
a = 2
else:
a = 1
cj_test = coint_johansen(data, 0, 1)
r0_trace, r1_trace = cj_test.lr1
r0_crit, r1_crit = cj_test.cvt[:, a]
co_int_vector = (cj_test.evec / cj_test.evec[0])[:, 0]
return r0_trace, r0_crit, r1_trace, r1_crit, co_int_vector
# Here I calculate the Johansen summary for each possible pair in my data set for pair in combinations(training_data.columns, 2):
p1, p2 = pair
pair_names.append(p1 + '-' + p2)
r0_t, r0_c, r1_t, r1_c, co_vec = coint_johansen_summary(training_data.loc[:, [p1, p2]].values, Trace_Quantil)
r0_trace.append(r0_t)
r0_crit.append(r0_c)
r1_trace.append(r1_t)
r1_crit.append(r1_c)
coint_vectors.append(co_vec)
results = pd.DataFrame(
data = np.concatenate(
(np.array(r0_trace).reshape(-1, 1), np.array(r0_crit).reshape(-1, 1),
np.array(r1_trace).reshape(-1, 1), np.array(r1_crit).reshape(-1, 1),
np.array(coint_vectors)),
axis = 1),
columns = ['r0_trace', 'r0_crit', 'r1_trace', 'r1_crit', 'co_vec1', 'co_vec2'],
index = pair_names
)
# Here I chose which pairs fit the trace statistics
co_int_pairs = results[(results.r0_trace > r0_crit) & (results.r1_trace <= r1_crit)]
# Here I then calculate the spread for the training data for each pair. Afterwards I would calculate the ADF and KPSS Test.
for row in co_int_pairs.iterrows():
pair = row[0]
hedge_ratio = row[1].loc['co_vec2']
df_pair = training_data.loc[:, pair.split('-')]
df_pair.loc[:, 'spread'] = df_pair.iloc[:, 0] - hedge_ratio * df_pair.iloc[:, 1]