量化投资第四次作业——ETF策略

  • *一点说明:该作业是寒假在家写的,所以完全敷衍草草了事,内容也是答辩…

我采用的是$ETF$股指期现套利策略。运行demo结果如下:

我发现代码并没有成功触发交易,其原因是期货价格始终没有达到触发交易的上下界,于是我考虑对上下界进行修改。通过查阅相关资料,我得出了上下界的确定函数:
$$
wtlxx=\frac{S_t(e^{(r-q)(T-t)}-2\beta-\sigma)}{1+2\alpha} \
wtlsx=\frac{S_t(e^{(r-q)(T-t)}+2\beta+\sigma)}{1-2\alpha}
$$
其中变量和参数的解释如下:

$S_t$表示现货股票在当前$t$时刻的价格。

$r$为无风险收益率。我粗略地选取央行一年期存款基准利率 $1.8%$。

$q$为股息收益率。由于我国股息分红很少,因此此处假设$q=0$

$\alpha$为期货交易成本。包含交易手续费,根据中国金融交易所发布调整交易手续费通知,因此这里取调整后的费率 $0.23%$。

$\beta$为现货$ETF$的交易成本。现如今由于$ETF$没有印花税等税收,故不考虑税率,只考虑佣金,假设管理手续费大概为成交金额的$0.2%$,托管费$0.1%$,合在一起约为$0.3%$。

$\sigma$为跟踪误差,一般来说跟踪误差不是一个固定值,但由于计算有些麻烦且对结果影响不大,故采用固定值。参考《上证 50 股指期货期现套利策略研究》中的统计结果,取 $\sigma=0.1496%$。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
a=instruments(context.contract).de_listed_date.date()
b=context.now.date()
delta=a-b
r = 0.018/180
q = 0
beta = 0.003
sigma = 0.001496
alpha = 0.0023
wtlxx = (contract_price[0]*(math.exp((r-q)*delta.days)-2*beta-sigma))/(2*alpha+1)
wtlsx = (contract_price[0]*(math.exp((r-q)*delta.days)+2*beta+sigma))/(-2*alpha+1)

由此确定新的上下界,从而进行套利。

在确定了新的上下界之后,我还添加了止损信号。在进行套利交易时,对于投资者来说,是常常伴有一定的风险的。而止损信号的目的就是把控交易风险,减少投资者的损失。具体来说,在交易活动中,收益一旦出现负收益,那么就存在一定的亏损,为了不让投资者的损失继续扩大恶化,投资者就要划定一个信号,一旦损失触碰到这个信号,投资者就要平仓停止交易,把损失控制在可控可接受的范围之内。当价格达到止损点时,自动进行平仓止损。

相关代码如下:

1
2
3
4
5
6
7
8
9
elif (contract_price[0] < wtlsx or (1000*sz50Etf_price[0]-contract_price[0])/contract_price[0]>0.12) and context.count == 1:   #正向套利平仓
youngquant.api.buy_close(context.contract,contract_xdss1)
order_target_value('510050.XSHG', 0)
context.count = 0

elif (contract_price[0] > wtlsx or (1000*sz50Etf_price[0]-contract_price[0])/contract_price[0]>0.12) and context.count == -1: #反向套利平仓
youngquant.api.sell_close(context.contract,contract_xdss2)
youngquant.api.order_shares('510050.XSHG',sz50Etf_xdgs)
context.count = 0

进行上述操作后,运行代码得到如下结果:

可以看出,在优化之后策略的年化收益率有了显著的提升,最大回撤也有了一定的降低,可见优化起到了一定的作用。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import youngquant
from youngquant.api import *
from youngquant import exec_strategy
import yqdata.services as yq
import datetime
import warnings
import math
warnings.filterwarnings('ignore')
config = {
"base": {
"start_date": "2020-05-01",
"end_date": "2020-07-31",
"frequency": "1d",
"benchmark": "000300.XSHG",
"plot": True,
"accounts": {
"stock": 10000000,
"FUTURE": 10000000,
},
"init_positions":'510050.XSHG:10000000'
},
"extra": {
"log_level": "error",
},
"mod": {
"sys_analyser": {
"enabled": True,
"plot": True
},
"mongodb": {
"enabled": True,
}
}
}

# def calculate_period(context, bar_dict):
# _list=[]
# contract_price = youngquant.api.history_bars(context.contract,30,'1d','close')

def update_contract(context, bar_dict):
if instruments(context.contract).de_listed_date.strftime("%Y-%m-%d") == context.now.date().strftime("%Y-%m-%d"):
year = context.now.strftime("%Y%m%d")[2:4]
a = context.now.strftime("%Y%m%d")[4:6]
if a[0]=='0':
month='0'+str(int(a[1])+1)
elif a[0]=='1' and a[1]=='2':
month='01'
else:
month='1'+str(int(a[1])+1)
context.contract = '%s%s%s'%(context.symbol,year,month)

def initialize(context):
context.count = 0
context.symbol = 'IH'
context.contract = 'IH2002'
scheduler.run_daily(update_contract, time_rule='before_trading')
# scheduler.run_monthly(calculate_period,tradingday=1, time_rule='before_trading')


def handle_data(context, bar_dict):
contract_price = youngquant.api.history_bars(context.contract,1,'1d','close')
sz50Etf_price = youngquant.api.history_bars('510050.XSHG',1,'1d','close')

a=instruments(context.contract).de_listed_date.date()
b=context.now.date()
delta=a-b
# wtlxx = ((contract_price[0]-30)*(1+(0.0196*delta.days)/360)-(contract_price[0]*0.02))/(1+(0.1*0.0196*delta.days)/360)
# wtlsx = (contract_price[0]*1.02*((1+(0.0196*delta.days)/360))+30)/(1-(0.1*0.0196*delta.days)/360)

r = 0.018/180
q = 0
beta = 0.003
sigma = 0.001496
alpha = 0.0023
wtlxx = (contract_price[0]*(math.exp((r-q)*delta.days)-2*beta-sigma))/(2*alpha+1)
wtlsx = (contract_price[0]*(math.exp((r-q)*delta.days)+2*beta+sigma))/(-2*alpha+1)

if contract_price[0] > wtlsx and context.count == 0: #卖出期货,买入etf现货,正向套利

global contract_xdss1
contract_xdss1=int(10000000/(contract_price[0]*instruments(context.contract).contract_multiplier))
youngquant.api.sell_open(context.contract,contract_xdss1)
contract_bzj=contract_price[0]*instruments(context.contract).contract_multiplier*contract_xdss1*instruments(context.contract).margin_rate
sz50Etf_xdss=int((10000000-contract_bzj)/(sz50Etf_price[0]*100))
order_lots('510050.XSHG', sz50Etf_xdss)
context.count = 1 #正向套利

elif contract_price[0] < wtlxx and context.count == 0: #买入期货,卖出etf现货,反向套利

global sz50Etf_xdgs
sz50Etf_xdgs=int(10000000/(sz50Etf_price[0]))
# sz50Etf_bzj=sz50Etf_xdgs*sz50Etf_price[0]*0.9 #假设融券保证金率为90%
sz50Etf_value=sz50Etf_xdgs*sz50Etf_price[0]
youngquant.api.order_shares('510050.XSHG',-sz50Etf_xdgs)
global contract_xdss2
contract_xdss2=int(sz50Etf_value/(contract_price[0]*instruments(context.contract).contract_multiplier))
youngquant.api.buy_open(context.contract,contract_xdss2)
# contract_bzj2=contract_price[0]*instruments(context.contract).contract_multiplier*contract_xdss2*instruments(context.contract).margin_rate
# 合约保证金率为10%,因为etf和合约的持有市值均小于1000万,所以两者合约金加起来也不超过1000万
context.count = -1

elif (contract_price[0] < wtlsx or (1000*sz50Etf_price[0]-contract_price[0])/contract_price[0]>0.12) and context.count == 1: #正向套利平仓
youngquant.api.buy_close(context.contract,contract_xdss1)
order_target_value('510050.XSHG', 0)
context.count = 0

elif (contract_price[0] > wtlsx or (1000*sz50Etf_price[0]-contract_price[0])/contract_price[0]>0.12) and context.count == -1: #反向套利平仓
youngquant.api.sell_close(context.contract,contract_xdss2)
youngquant.api.order_shares('510050.XSHG',sz50Etf_xdgs)
context.count = 0
else:
pass

exec_strategy(initialize=initialize, handle_data=handle_data, config=config)