首页 > 编程知识 正文

股票机器人目前提供的操作策略包括

时间:2023-05-05 04:27:14 阅读:250338 作者:2380

《Python Machine Learning Blueprints》 Alexander T.Combs 著
《Python机器学习实践指南》黄申 译
读书笔记2

本文不构成任何投资建议,仅供学习。
文章所用数据集来自yahoo网站SPY ETF的标普500股票交易数据,需要安装datareader包从网站拉取数据,可以使用pip命令安装:pip install pandas_datareader。下文每个代码块对应一个输出,有的贴出了输出结果,有的未贴出。

目录 如何开发一个交易策略延长分析周期使用支持向量回归构建模型评估模型性能 建模与动态时间扭曲

如何开发一个交易策略

首先导入包,拉取SPY ETF从2010年初到2016年3月初的数据。

import pandas as pdimport numpy as npfrom pandas_datareader import data, wbimport matplotlib.pyplot as plt# 设置格式%matplotlib inlinepd.set_option('display.max_colwidth', 200)# 拉取数据import pandas_datareader as pdrstart_date = pd.to_datetime('2010-01-01')stop_date = pd.to_datetime('2016-03-01')spy = pdr.data.get_data_yahoo('SPY', start_date, stop_date)spy # 显示数据


绘制数据(数据可视化),只选择收盘价

spy_c = spy['Close'] # 选择收盘价绘制数据fig, ax = plt.subplots(figsize=(15, 10))spy_c.plot(color='k')plt.title('SPY', fontsize=20)


从图中我们看到选定时期内,标准普尔500指数日收盘价的价格图。接下来进一步分析,如果投资这个ETF,该期间内的回报时多少。
先拉取首个开盘日的数据。

first_open = spy['Open'].iloc[0]first_open


接下来,让我们得到该期间最后一天的收盘价

last_close = spy['Close'].iloc[-1]last_close

last_close - first_open # 看看整个时期的变化,可知收益超过76% spy['Daily Change'] = pd.Series(spy['Close'] - spy['Open'])spy['Daily Change'] # 每天从开盘到收盘的变化

# 将这段时期的变化加和spy['Daily Change'].sum()

从上面代码的结果看来,我们的收益已经从超过85点的增长,下降到刚刚过41的增长,也就是说,我们一般以上的市场收益来自这段时间内整日整夜地持有股票!隔夜交易的回报率优于盘中交易的回报率,那么它的波动性如何呢?我们可以使用numpy来计算盘中交易的标准差

np.std(spy['Daily Change']) spy['Overnight Change'] = pd.Series(spy['Open'] - spy['Close'].shift(1))np.std(spy['Overnight Change']) # 计算隔夜交易的标准差

上面两个代码块分别输出盘中交易的标准差以及隔夜交易的标准差,分别约为1.14和0.95,因此隔夜交易相比于盘中交易具有较低的波动性。
下面比较下跌交易日的平均变化。

# 计算下跌交易日的每日变化spy[spy['Daily Change']<0]['Daily Change'].mean() # 计算下跌交易日的隔夜变化spy[spy['Overnight Change']<0]['Overnight Change'].mean()

输出结果分别约为-0.90,-0.66,可以看出隔夜交易日策略的平均下降幅度小于盘中交易策略。

到目前为止,我们观测的都是数据点,现在看看回报。浙江有助于在更现实的背景下讨论我们的收益和损失。继续前面的两个策略,我们将为每个场景构建一个pandas数据序列:每日回报(昨日收盘到今日收盘的价格变化)、盘中回报(当日开盘到收盘的价格变化)和隔夜回报(昨日收盘到今日开盘的价格变化),具体如下。

daily_rtn = ((spy['Close'] - spy['Close'].shift(1))/spy['Close'].shift(1)*100)id_rtn = ((spy['Close'] - spy['Open'])/spy['Open'])*100on_rtn = ((spy['Open'] - spy['Close'].shift(1))/spy['Close'].shift(1))*100daily_rtn

下面来看看这两个策略的统计信息。我们将创建一个函数,它接收每个回报的数据序列,然后打印出摘要性的结果。我们要得到每一次获利、亏损和盈亏平衡交易的统计数据,以及名为夏普比率的东西。

def get_stats(s, n=252): s = s.dropna() wins = len(s[s>0]) losses = len(s[s<0]) evens = len(s[s==0]) mean_w = round(s[s>0].mean(), 3) mean_l = round(s[s<0].mean(), 3) win_r = round(wins/losses, 3) mean_trd = round(s.mean(), 3) sd = round(np.std(s), 3) max_l = round(s.min(), 3) max_w = round(s.max(), 3) sharpe_r = round((s.mean()/np.std(s))*np.sqrt(n), 4) cnt = len(s) print('Trades:', cnt, 'nWins:',wins,'nLosses:', losses, 'nBreakeven:', evens, 'nWin/Loss Ratio', win_r, 'nMean Win:', mean_w,'nMean Loss:', mean_l, 'nMean:', mean_trd,'nStd Dev:',sd, 'nMax Loss:', max_l, 'nMax Win:', max_w,'nSharpe Ratio:', sharpe_r) get_stats(daily_rtn)

get_stats(on_rtn)


因数据拉取的不同,统计数据会有所差异,无伤大雅。

延长分析周期

现在延长我们的分析。首先,从标普500指数拉取自2000年开始的数据。

start_date = pd.to_datetime('2000-01-01')stop_date = pd.to_datetime('2016-03-01')sp = pdr.data.get_data_yahoo('SPY', start_date, stop_date)# 将数据可视化fig, ax = plt.subplots(figsize=(15,10))sp['Close'].plot(color='k')plt.title('SPY', fontsize=20)

在这个新扩展的时间段内,获取三个基本策略的基准线。
首先,为每个策略设置变量。

long_day_rtn = ((sp['Close'] - sp['Close'].shift(1))/sp['Close'].shift(1))*100long_id_rtn = ((sp['Close'] - sp['Open'])/sp['Open'])*100long_on_rtn = ((sp['Open'] - sp['Close'].shift(1))/sp['Close'].shift(1))*100# 看看每个策略的总体数据,下面生成三个输出结果(sp['Close'] - sp['Close'].shift(1)).sum() # 每日回报(sp['Close'] - sp['Open']).sum() # 盘中回报(sp['Open'] - sp['Close'].shift(1)).sum() # 隔夜回报

继续看每种策略的统计数据

get_stats(long_day_rtn) # 每日回报的统计量 get_stats(long_id_rtn) # 盘中回报统计量 get_stats(long_on_rtn) # 隔夜回报统计量

上述结果可以看出,在更长的考察期内,三者之间的差异更加显著。

使用支持向量回归构建模型

现在我们有一个基线用于比较,接下来构建第一个回归模型。我们将从一个非常基本的模型开始,只使用股票的前一天的收盘价值来预测第二天的收盘价。我们将使用支持向量回归来构建此模型。
第一步是为包含每一天价格的历史记录设置DataFrame对象。在这个模型中,我们将包含过去的20个收盘。

for i in range(1, 21, 1): sp.loc[:, 'Close Minus' + str(i)] = sp['Close'].shift(i)sp20 = sp[[x for x in sp.columns if 'Close Minus' in x or x == 'Close']].iloc[20:,]sp20

颠倒这些列,这样从左到右就是最早时间到最晚时间的顺序。

sp20 = sp20.iloc[:, ::-1]sp20

导入支持向量机并设置训练和测试矩阵,以及每个数据点的目标向量

from sklearn.svm import SVRclf = SVR(kernel='linear')x_train = sp20[:-1000]y_train = sp20['Close'].shift(-1)[:-1000]x_test = sp20[-1000:]y_test = sp20['Close'].shift(-1)[-1000:] # 我们只有4000个数据点可以使用,选择使用最后的1000个作为测试# 拟合模型,并使用它来测试样本之外的数据model = clf.fit(x_train, y_train)preds = model.predict(x_test) # 将预测值与实际数据进行比较tf = pd.DataFrame(list(zip(y_test, preds)), columns=['Next Day Close', 'Predicted Next Close'], index=y_test.index)tf

评估模型性能

向DataFrame对象添加一些额外的数据点来计算结果。

cdc = sp[['Close']].iloc[-1000:]ndo = sp[['Open']].iloc[-100:].shift(-1)tf1 = pd.merge(tf, cdc, left_index=True, right_index=True)tf2 = pd.merge(tf1, ndo, left_index=True, right_index=True)tf2.columns = ['Next Day Close', 'Predicted Next Close', 'Current Day Close', 'Next Day Open']tf2

添加以下代码来获取收益和亏损的信号量

x_train = sp20[:-2000]y_train = sp20['Close'].shift(-1)[:-2000]x_test = sp20[-2000:-1000]y_test = sp20['Close'].shift(-1)[-2000:-1000]model = clf.fit(x_train, y_train)preds = model.predict(x_test)tf = pd.DataFrame(list(zip(y_test, preds)), columns=['Next Day Close', 'Predicted Next Close'], index=y_test.index)cdc = sp[['Close']].iloc[-2000:-1000]ndo = sp[['Open']].iloc[-2000:-1000].shift(-1)tf1 = pd.merge(tf, cdc, left_index=True, right_index=True)tf2 = pd.merge(tf1, ndo, left_index=True, right_index=True)tf2.columns = ['Next Day Close', 'Predicted Next Close', 'Current Day Close', 'Next Day Open']def get_signal(r): if r['Predicted Next Close'] > r['Next Day Open'] + 1: return 1 else: return 0def get_ret(r): if r['Signal'] == 1: return ((r['Next Day Close'] - r['Next Day Open'])/r['Next Day Open'])*100 else: return 0tf2 = tf2.assign(Signal = tf2.apply(get_signal, axis=1))tf2 = tf2.assign(PnL = tf2.apply(get_ret, axis=1))(tf2[tf2['Signal']==1]['Next Day Close'] - tf2[tf2['Signal']==1]['Next Day Open']).sum()

下面使用另一个算法模型,动态时间规整(dynamic time warping),它所做的事情是向你提供一个表示两个时间序列之间相似性的度量。先从命令行使用pip安装fastdtw,输入pip install fastdtw.

建模与动态时间扭曲 # 导入附加库from scipy.spatial.distance import euclideanfrom fastdtw import fastdtw# 创建一个函数,该函数将接收两个序列并返回它们之间的距离。def dtw_dist(x, y): distance, path = fastdtw(x, y, dist=euclidean) return distance# 将16年的时间序列数据分成不同的期间,每个期间长度为5天。# 为每个期间加上一个附加的点,用于创建我们的x和y数据tseries = []tlen = 5for i in range(tlen, len(sp), tlen): pctc = sp['Close'].iloc[i-tlen:i].pct_change()[1:].values*100 res = sp['Close'].iloc[i-tlen:i+1].pct_change()[-1]*100 tseries.append((pctc, res))# 看看第一个序列tseries[0]

现在有了序列,就可以通过算法运行它们来获得每个序列相对于其他序列的距离度量。

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]))# 将其放入一个DataFram对象,删除相互距离为0的序列,根据序列的日期进行排序,只观测第一个序列在时间上排第二个序列之前的那些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']<sf['B']]# 将交易限制到相互距离为1,而第一个序列的回报为正的情况winf = sfe[(sfe['Dist']<=1)&(sfe['A Ret']>0)]winf

让我们看看排名靠前的模式在绘制后是什么样子

plt.plot(np.arange(4), tseries[6][0])

绘制第二个

plt.plot(np.arange(4), tseries[598][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)) # 评估最终结果

这些结果是迄今为止我们看到的最好结果。看来这个新模型行得通!
为了进一步检视该结果,我们应该通过其他的时间段来探索其鲁棒性。周期超过四天是否会改善模型?是否应该排除产生亏损的模式?

数据结构与算法(java版)第二季 - 6 并查集

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。