Python实现基于动态时间规整的股市交易策略测试

Python实现基于动态时间规整的股市交易策略测试

  • 一、策略简介
  • 二、策略原理
  • 三、代码实现
  • 四、测试策略
  • 五、完整代码

一、策略简介

该项目采用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年的历史数据,并取出收盘价数据。可以画个图看一下:
Python实现基于动态时间规整的股市交易策略测试_第1张图片
分别设置三个参量:时间序列的长度,持有天数,判定相似度高的阈值。

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越低代表相似度越高。
然后清理掉以下情况的数据:

  1. 相似度为0,即相同的两个序列。
  2. 两个序列太近,中间还没超过一个完整的持有周期。
  3. 距离超过阈值,即相似度过低的两个序列。
  4. 第一个序列的“交易”回报为负。
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))

你可能感兴趣的:(金融,python)