idea
目前在配对交易的识别上比较有代表性的是根据二者的相关性来判断,也有根据标的之间协整性来进行选择的,在这里我们结合两种方式进行综合选取,即在相关性较高的合约对之中选取具有显著协整性关系的合约对,并设置一定的止损率进行投资策略的改进。
数据介绍
我们有38只期货合约的tick级快照数据,每只合约的数据如下:
其中的数据时间戳为100纳秒数据,并且开始于0001年1月1日,因此在这里将其转化为现实数据,并转化为分钟数据:
最终的每个合约的分钟数据如下,基于此数据进行配对交易。
配对交易
寻找配对标的
相关性
计算合约标的中国两两之间的相关性大小:
OK,合约太多看不清楚,我们选取部分结果展示如下,在这里不变透露合约名称,因此用合约id代表:
在这里我们应该选取相关性较高的几对合约,选取阈值为0.7,即选取相关性大于0.7的合约对储存于变量pairs:
这里共有42对合约满足条件,进而判断合约对之间的协整性。
协整性
在进行协整性检验之前首先要验证数据是否为一阶单整的,即原始数据非平稳而一阶差分之后变为平稳的。我们以raw_clp列为检验原始数据是否为平稳的结果,diff_clp列为检验一阶差分之后的数据是否为平稳的结果,平稳为1,非平稳为0,显著性水平为0.01。可以看到数据满足一阶单整的条件,进而判断是否为协整的。
可以看到合约池中的合约都满足一阶单整的条件,可以进行协整性的判定。
最后选出了四对具有协整性(在显著性水平
α
=
0.01
\alpha=0.01
α=0.01条件下)的合约,从中选取相关性较大的前三对(121408与43409, 681409和791409, 681409与851410)进行配对交易。
策略构建
以681409与791409为例,做出二者中心化之后的价差,可以看到价差数据在0附近波动,我们设置一定的阈值,例如正负一个(或者两个标准差
σ
σ
σ)作为均值回复的区间进而在价差高于给定阈值时即价差值大于σ时做空高价格标的做多低价格标的,而价差值小于
−
σ
-σ
−σ时反向操作。
在实际的操作中不同的品种对应该设置不同的阈值。
下面进入策略构建环节strat_pair(id=0, sig=1.8, loss=0.05):
(具体代码有需要可以私信我:))
首先设置参数如下:
id:合约对的id,在这里我们选出了三个(0, 38, 39)
sig:为标准差的倍数,将其乘以标准差后作为上下界阈值,如果当前价差值在上界做空,反之做多。
loss:止损率,如果当前一笔交易的收益为负,并且其绝对值小于总收益的loss倍数,则平仓止损。
分别展示出三对合约的策略的参数以及对应的输出信息(在实际的测试当中当选取sig=1.8,loss=0.03时,发现在三对合约上都有较好的表现,因此本策略具有较强的鲁棒性)
回测结果
Id=0时:strat_pair(id=0, sig=1.8, loss=0.03)
Id=38时:
Id=39时:
总结
此策略在传统的配对交易上加入了止损,同时,在筛选标的对的时候结合了相关性以及协整性,使得筛选出来的标的对更符合配对交易的宗旨,可以看到具有很好的效果。当然有待改进的地方也有几处,例如如何权衡止损率和换手率之间的关系?欢迎小伙伴批评指教!
核心代码示例
def find_pairs(val):
clp_data = pd.read_csv('min_clp_data.csv')
clp_data.index = clp_data[['date', 'time']]
close_data = clp_data.drop(columns=['date', 'time'])
sns.heatmap(close_data.corr(), annot=True, square=True)
plt.show()
cor = close_data.corr()
A = list()
B = list()
C = list()
for i in range(len(cor))[1:]:
for j in range(i + 1, len(cor)):
if cor.iloc[i, j] > val:
A.append(cor.index[i])
B.append(cor.index[j])
C.append(cor.iloc[i, j])
pairs = pd.DataFrame([A, B, C], index=['cont1', 'cont2', 'cor_val'])
return pairs, close_data
def check_pairs(pairs, close_data):
raw = list()
one_lag = list()
for i in range(pairs.shape[1]):
res1 = adfuller(np.diff(close_data[pairs[i].iloc[0]]))
res2 = adfuller(np.diff(close_data[pairs[i].iloc[1]]))
if res1[0] < res1[4]['1%'] and res2[0] < res2[4]['1%']:
one_lag.append('1')
else:
one_lag.append('0')
res1 = adfuller(close_data[pairs[i].iloc[0]])
res2 = adfuller(close_data[pairs[i].iloc[1]])
if res1[0] < res1[4]['1%'] and res2[0] < res2[4]['1%']:
raw.append('1')
else:
raw.append('0')
check = pd.DataFrame([raw, one_lag], index=['raw_clp', 'diff_clp']).T
coint_res = list()
for i in range(pairs.shape[1]):
co_val = coint(close_data[pairs[i].iloc[0]], close_data[pairs[i].iloc[1]])
if co_val[0] <= co_val[2][2]:
coint_res.append(1)
else:
coint_res.append(0)
coint_res = pd.DataFrame(coint_res)
var = coint_res[coint_res[0] == 1].index
final_pairs = pairs[coint_res[coint_res[0] == 1].index]
return final_pairs
def strat_pair(id=0, sig=1.8, loss=0.05):
pos = [0, ]
ctlist = pd.read_csv('ContractList.csv')
cs0 = ctlist[ctlist['ContractId'] == int(pairs[id].iloc[0])]['ContractSize'].iloc[0]
cs1 = ctlist[ctlist['ContractId'] == int(pairs[id].iloc[1])]['ContractSize'].iloc[0]
clp = close_data[pairs[id][:2]]
jc = close_data[pairs[id].iloc[1]] - close_data[pairs[id].iloc[0]]
jc_val = jc - np.mean(jc)
sigma = np.std(jc_val)
pro = list()
pro.append(10000)
up_val = sig * sigma
down_val = - up_val
for i in range(len(jc_val))[1:]:
if pos[-1] == 0:
if jc_val[i] >= up_val:
ret1 = (clp.iloc[i - 1, 1] - clp.iloc[i, 1]) * cs1
ret0 = (clp.iloc[i, 0] - clp.iloc[i - 1, 0]) * cs0
pro.append(ret1 + ret0)
pos.append(-1)
elif jc_val[i] <= down_val:
ret1 = (clp.iloc[i, 1] - clp.iloc[i - 1, 1]) * cs1
ret0 = (clp.iloc[i - 1, 0] - clp.iloc[i, 0]) * cs0
pro.append(ret1 + ret0)
pos.append(1)
else:
pro.append(0)
pos.append(0)
else:
if pos[-1] == -1:
if jc_val[i] > down_val:
ret1 = (clp.iloc[i - 1, 1] - clp.iloc[i, 1]) * cs1
ret0 = (clp.iloc[i, 0] - clp.iloc[i - 1, 0]) * cs0
if ret1 + ret0 < 0 and abs(ret1 + ret0) < sum(pro) * loss:
pro.append(0)
pos.append(0)
continue
pro.append(ret1 + ret0)
pos.append(-1)
else:
ret1 = (clp.iloc[i, 1] - clp.iloc[i - 1, 1]) * cs1
ret0 = (clp.iloc[i - 1, 0] - clp.iloc[i, 0]) * cs0
if ret1 + ret0 < 0 and abs(ret1 + ret0) < sum(pro) * loss:
pro.append(0)
pos.append(0)
continue
pro.append(ret1 + ret0)
pos.append(1)
else:
if jc_val[i] < up_val:
ret1 = (clp.iloc[i, 1] - clp.iloc[i - 1, 1]) * cs1
ret0 = (clp.iloc[i - 1, 0] - clp.iloc[i, 0]) * cs0
if ret1 + ret0 < 0 and abs(ret1 + ret0) < sum(pro) * loss:
pro.append(0)
pos.append(0)
continue
pro.append(ret1 + ret0)
pos.append(1)
else:
ret1 = (clp.iloc[i - 1, 1] - clp.iloc[i, 1]) * cs1
ret0 = (clp.iloc[i, 0] - clp.iloc[i - 1, 0]) * cs0
if ret1 + ret0 < 0 and abs(ret1 + ret0) < sum(pro) * loss:
pro.append(0)
pos.append(0)
continue
pro.append(ret1 + ret0)
pos.append(-1)
profit = np.cumsum(pro)
fig = plt.figure()
ax = plt.subplot(211)
ax.plot(profit)
ax.set_ylabel('profit')
ax = plt.subplot(212)
ax.plot(pos)
ax.set_ylabel('position')
ax.set_xlabel('t')
p = (pairs[id].iloc[0], pairs[id].iloc[1])
return pos, profit, p
代码以及数据链接: https://pan.baidu.com/s/1oRdBkdg9hqf9AppreUFFeA 提取码: i19x