移动平均线(MA)交易策略回测

前言

前几天在翻查书籍的时候看到了书上介绍用42日与252日的移动平均线(Moving Average)的穿插作为交易信号并设置了最低阀值(意思是就算出现了交易信息,但移动值必须大于或小于最低阀值才会交易),我看了看觉得蛮有趣的,于是乎将其结合Tushare应用到国内的A股股票中。

免费注册Tushare.pro账号

首先,我们需要从tushare.pro注册一个账号并调用其API获取股票日线数据。可能大多数的童稚是第一次接触tushare.pro, 那我就直接贴上官方介绍:

Tushare是一个免费提供各类金融数据和区块链数据 , 助力智能投资与创新型投资的python财经数据接口包。拥有丰富的数据内容,如股票、基金、期货、数字货币等行情数据,公司财务、基金经理等基本面数据。

过程

1
2
3
4
5
6
import tushare as ts
import pandas as pd
import numpy as np
import scipy.stats
import matplotlib.pyplot as plt
pro = ts.pro_api('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') #这里需要填写你注册好的Tushare的TOKEN凭证

通过调用tushare获取股票600377(宁沪高速)的股票数据,这里不设置日期,那么默认获取Tushare提供的历史数据。

1
2
ticker_data = pro.daily(ts_code='600377.SH')
ticker_data.info()

输出:

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4000 entries, 0 to 3999
Data columns (total 11 columns):
ts_code       4000 non-null object
trade_date    4000 non-null object
open          4000 non-null float64
high          4000 non-null float64
low           4000 non-null float64
close         4000 non-null float64
pre_close     4000 non-null float64
change        4000 non-null float64
pct_chg       4000 non-null float64
vol           4000 non-null float64
amount        4000 non-null float64
dtypes: float64(9), object(2)
memory usage: 343.8+ KB

从上面可以看出,序号并不是以时间作为单位的。那么我们首先需要将trade_date转为datetime格式,然后设置为序号以便于画图。

1
2
3
4
5
6
7
8
ticker_data['trade_date'] = pd.to_datetime(ticker_data['trade_date'],format='%Y%m%d')
ticker_data.sort_values(by='trade_date', inplace=True)
ticker_data.set_index('trade_date', inplace=True)
returns = ticker_data["close"].pct_change().dropna()

plt.figure(figsize=(15, 5))
plt.title("股票代码:600377 - 宁沪高速", weight='bold')
ticker_data['close'].plot()

输出,下图为收盘价的日线图:

然后我们再计算出42日和252日的移动平均线:

1
2
3
4
ticker_data['42d'] = np.round(ticker_data['close'].rolling(window=42).mean(),2)
ticker_data['252d'] = np.round(ticker_data['close'].rolling(window=252).mean(),2)

ticker_data[['close','42d','252d']].tail()

输出:

- close 42d 252d
trade_date
2019-01-28 9.80 9.70 9.37
2019-01-29 9.98 9.71 9.37
2019-01-30 9.86 9.72 9.37
2019-01-31 9.85 9.73 9.37
2019-02-01 9.89 9.74 9.37
1
ticker_data[['close','42d','252d']].plot(grid=True, figsize=(15, 5))

输出:

1
2
ticker_data['42-252'] = ticker_data['42d'] - ticker_data['252d']
ticker_data['42-252'].tail()

输出:

trade_date
2019-01-28    0.33
2019-01-29    0.34
2019-01-30    0.35
2019-01-31    0.36
2019-02-01    0.37
Name: 42-252, dtype: float64
1
ticker_data['42-252'].head()

输出:

trade_date
2002-05-08   NaN
2002-05-09   NaN
2002-05-10   NaN
2002-05-13   NaN
2002-05-14   NaN
Name: 42-252, dtype: float64

这里我们设置阀值为1, 你可以设置为其他阀值作测试。

1
2
3
4
SD = 1
ticker_data['Regime'] = np.where(ticker_data['42-252'] > SD, 1, 0)
ticker_data['Regime'] = np.where(ticker_data['42-252'] < -SD, -1, ticker_data['Regime'])
ticker_data['Regime'].value_counts()

输出:

 0    3012
-1     640
 1     348
Name: Regime, dtype: int64

这说明了买入的股票的持续天数为348日,卖出为640日, 空仓天数为3012日。

1
2
ticker_data['Regime'].plot(lw=1.5,figsize=(15, 5))
plt.ylim([-1.1, 1.1])

输出:

1
ticker_data['Market'] = np.log(ticker_data['close'] / ticker_data['close'].shift(1))
1
ticker_data['Strategy'] = ticker_data['Regime'].shift(1) * ticker_data['Market']
1
ticker_data[['Market', 'Strategy']].cumsum().apply(np.exp).plot(grid=True,figsize=(15, 5))

输出:

结论

从上面的图可以看出,基本上都是以空仓为主,交易次数不算多,但足以跑赢买入持有策略(buy and hold)。 但需要注意的是,这样的交易策略和交易次数是与我上面设置的阀值为1有关,这个值比较随意,导致不同的阀值有不同的结果。

0%