该项目采用Python语言,利用动态时间规整算法对沪深300市场构建一种交易策略,并采用夏普比率来对该策略模拟交易的结果进行评估。
本项目只是对交易策略的初探,不涉及投资组合、风险缓释和资金管理等等,也不被视为任何类型的投资建议!欢迎相关专业人士指教。
动态时间规整是一种衡量两段时间序列的相似度的算法,具体运算细节这里不多赘述。
我们将过去一段时间的沪深300每日涨跌率(当天收盘价与前一天收盘价相比)按照特定天数分成若干个时间序列,并设置持有时间。
为描述方便,商定:在时间序列结尾买入,过了持有时间后卖出,算作一次完整“交易”。
该策略会将正在被测试的时期与过去的若干已知时间序列进行动态时间规整并找到相似度高的时间序列,若这些时间序列的“交易”得到了正收益,我们就期望在现时期进行一次“交易”。
使用到的库:
import pandas as pd
import matplotlib.pyplot as plt
from fastdtw import fastdtw
from scipy.spatial.distance import euclidean
import numpy as np
import math
pandas和numpy帮助我们进行数据处理,plt用来画图,fastdtw用来进行动态时间规整。
spy = pd.read_csv('300.csv')
spy = spy.set_index(['Date'])
spy_c = spy['Close']
读取沪深300从2005年到2021年的历史数据,并取出收盘价数据。可以画个图看一下:
分别设置三个参量:时间序列的长度,持有天数,判定相似度高的阈值。
tlen = 5
hold = 7
thre = 5
计算每日涨跌率并按照tlen分成若干时间序列,并计算每个时间序列对应的一次完整“交易”的回报。将两者放入tseries中:
tseries = []
for i in range(tlen,len(spy_c)-hold-1,tlen):
pctc = spy_c.iloc[i-tlen:i].pct_change()[1:].values*100
res = (spy_c[i+hold+1] - spy_c[i])/spy_c[i] * 100
tseries.append((pctc,res))
对每个序列都与其他序列依次计算相似度,并保存两个序列的“交易”回报。dist越低代表相似度越高。
然后清理掉以下情况的数据:
def dtw_dist(x, y):
distance, path = fastdtw(x, y, dist=euclidean)
return distance
dist_pairs = []
for i in range(len(tseries)):
for j in range(len(tseries)):
dist = dtw_dist(tseries[i][0],tseries[j][0])
dist_pairs.append((i,j,dist,tseries[i][1],tseries[j][1]))
dist_frame = pd.DataFrame(dist_pairs,columns=['A','B','Dist','A Ret','B Ret'])
sf = dist_frame[dist_frame['Dist']>0].sort_values(['A','B']).reset_index(drop=1)
sfe = sf[sf['A']+math.ceil(float(tlen+hold)/tlen) <= sf['B']]
winf = sfe[(sfe['Dist']<=thre)&(sfe['A Ret']>0)]
为评估我们的测试结果,我们定义一个评估函数:
def get_stats(s,n=252):
cnt = len(s)
wins = len(s[s>0])
losses = len(s[s<0])
mean_trd = round(s.mean(),3)
sd = round(np.std(s),3)
sharpe_r = round((s.mean()/np.std(s))*np.sqrt(n),4)
print 'Trades:',cnt,\
'\nWins:',wins,\
'\nLosses:',losses,\
'\nMean:',mean_trd,\
'\nStd Dev:',sd,\
'\nSharpe Ratio:',sharpe_r
输入该策略的所有模拟交易回报序列,返回交易次数、盈利次数、亏损次数、平均收益率、回报标准差、修正后的夏普比率。
为方便对该策略的结果有个直观的比较,我们取一个新的策略——固定每天以开盘价买入,以收盘价卖出,作为我们的基准线。
>>> id_rtn = ((spy['Close'] - spy['Open'])/spy['Open'])*100
>>> get_stats(id_rtn)
Trades: 3920
Wins: 2121
Losses: 1799
Mean: 0.127
Std Dev: 1.555
Sharpe Ratio: 1.3008
现在进行我们的策略的评估。
对于相似的有正盈利的历史序列,我们会进行交易。如果发生了亏损的情况我们就删除相应历史序列。
excluded = {
}
return_list = []
def get_returns(r):
if excluded.get(r['A']) is None:
return_list.append(r['B Ret'])
if r['B Ret']<0:
excluded.update({
r['A']:1})
winf.apply(get_returns,axis=1)
看看我们的策略的评估结果:
>>> get_stats(pd.Series(return_list))
>Trades: 1180
Wins: 753
Losses: 427
Mean: 1.665
Std Dev: 4.913
Sharpe Ratio: 5.3795
可以看到相比基准策略有了更高的夏普比率。
import pandas as pd
import matplotlib.pyplot as plt
from fastdtw import fastdtw
from scipy.spatial.distance import euclidean
import numpy as np
import math
def dtw_dist(x, y):
distance, path = fastdtw(x, y, dist=euclidean)
return distance
def get_stats(s,n=252):
cnt = len(s)
wins = len(s[s>0])
losses = len(s[s<0])
mean_trd = round(s.mean(),3)
sd = round(np.std(s),3)
sharpe_r = round((s.mean()/np.std(s))*np.sqrt(n),4)
print 'Trades:',cnt,\
'\nWins:',wins,\
'\nLosses:',losses,\
'\nMean:',mean_trd,\
'\nStd Dev:',sd,\
'\nSharpe Ratio:',sharpe_r
spy = pd.read_csv('300.csv')
spy = spy.set_index(['Date'])
#spy = spy.sort_values(['Date'])
spy_c = spy['Close']
tlen = 5
hold = 7
thre = 5
tseries = []
for i in range(tlen,len(spy_c)-hold-1,tlen):
pctc = spy_c.iloc[i-tlen:i].pct_change()[1:].values*100
res = (spy_c[i+hold+1] - spy_c[i])/spy_c[i] * 100
tseries.append((pctc,res))
dist_pairs = []
for i in range(len(tseries)):
for j in range(len(tseries)):
dist = dtw_dist(tseries[i][0],tseries[j][0])
dist_pairs.append((i,j,dist,tseries[i][1],tseries[j][1]))
dist_frame = pd.DataFrame(dist_pairs,columns=['A','B','Dist','A Ret','B Ret'])
sf = dist_frame[dist_frame['Dist']>0].sort_values(['A','B']).reset_index(drop=1)
sfe = sf[sf['A']+math.ceil(float(tlen+hold)/tlen) <= sf['B']]
winf = sfe[(sfe['Dist']<=thre)&(sfe['A Ret']>0)]
excluded = {
}
return_list = []
def get_returns(r):
if excluded.get(r['A']) is None:
return_list.append(r['B Ret'])
if r['B Ret']<0:
excluded.update({
r['A']:1})
winf.apply(get_returns,axis=1)
get_stats(pd.Series(return_list))