前言

本文介绍了一个股票交易的基础回测框架,并且通过均线择时策略,详细的演示了策略搭建的过程,和大家共同学习交流。

  1. 文章示例中使用的‘000001.XSHE.csv’文件,已上传至csdn资源中,可直接下载
  2. 或者关注文末公众号:‘阿旭算法与机器学习’。然后输入:股票数据,即可获取4000多只股票从2015年1月至2022年10月的所有基础数据

1. 导入数据

import pandas as pd
import matplotlib.pyplot as plt
pd.set_option('display.max_columns', None) # 展示所有列
# 000001.XSHE.csv文件包含2015年1月至2022年10月的开盘价、收盘价、最高价、最低价、成交量、成交金额股票的基本数据
df = pd.read_csv('./000001.XSHE.csv')  
df.head()
date open close high low volume money
0 2015-01-05 9.98 10.00 10.17 9.74 458099037.0 4.565388e+09
1 2015-01-06 9.90 9.85 10.23 9.71 346952496.0 3.453446e+09
2 2015-01-07 9.72 9.67 9.88 9.55 272274401.0 2.634796e+09
3 2015-01-08 9.68 9.34 9.72 9.30 225445502.0 2.128003e+09
4 2015-01-09 9.30 9.42 9.91 9.19 401736419.0 3.835378e+09
# 求该股票每日涨跌幅
df['change_pct'] = df['close'].pct_change()
df = df[['date','open','close','high','low','change_pct']]
df['date'] = pd.to_datetime(df['date'])  # 将交易日期字符串变为日期类型
df.head()
date open close high low change_pct
0 2015-01-05 9.98 10.00 10.17 9.74 NaN
1 2015-01-06 9.90 9.85 10.23 9.71 -0.015000
2 2015-01-07 9.72 9.67 9.88 9.55 -0.018274
3 2015-01-08 9.68 9.34 9.72 9.30 -0.034126
4 2015-01-09 9.30 9.42 9.91 9.19 0.008565

2. 交易框架搭建–以均线策略为例

当短期均线由下向上穿过长期均线的时候,第二天以开盘价全仓买入并在之后一直持有;

当短期均线由上向下穿过长期均线的时候,第二天以开盘价全仓卖出,之后空仓,直到下次买入

2.1 计算均线数值

此处以短期均线MA=5;长期均线MA=20为例进行说明,至于参数的优化,需要依据不同股票的实际情况修改

# 计算均线
ma_short = 5  #短期均线,ma代表:moving_average
ma_long = 20  #长期均线,ma代表:moving_average
df['ma_short'] = df['close'].rolling(ma_short).mean()
df['ma_long'] = df['close'].rolling(ma_long).mean()
df.head(10)
date open close high low change_pct ma_short ma_long
0 2015-01-05 9.98 10.00 10.17 9.74 NaN NaN NaN
1 2015-01-06 9.90 9.85 10.23 9.71 -0.015000 NaN NaN
2 2015-01-07 9.72 9.67 9.88 9.55 -0.018274 NaN NaN
3 2015-01-08 9.68 9.34 9.72 9.30 -0.034126 NaN NaN
4 2015-01-09 9.30 9.42 9.91 9.19 0.008565 9.656 NaN
5 2015-01-12 9.29 9.22 9.40 9.05 -0.021231 9.500 NaN
6 2015-01-13 9.15 9.17 9.30 9.12 -0.005423 9.364 NaN
7 2015-01-14 9.23 9.25 9.49 9.18 0.008724 9.280 NaN
8 2015-01-15 9.27 9.58 9.58 9.19 0.035676 9.328 NaN
9 2015-01-16 9.62 9.60 9.75 9.48 0.002088 9.364 NaN
#补全上面均线缺失值:补全方式采用扩展窗口函数expanding,移动计算前面所有值之和的均值
df['ma_short'].fillna(value=df['close'].expanding().mean(),inplace=True)
df['ma_long'].fillna(value=df['close'].expanding().mean(),inplace=True)
df.head(10)
date open close high low change_pct ma_short ma_long
0 2015-01-05 9.98 10.00 10.17 9.74 NaN 10.000 10.000000
1 2015-01-06 9.90 9.85 10.23 9.71 -0.015000 9.925 9.925000
2 2015-01-07 9.72 9.67 9.88 9.55 -0.018274 9.840 9.840000
3 2015-01-08 9.68 9.34 9.72 9.30 -0.034126 9.715 9.715000
4 2015-01-09 9.30 9.42 9.91 9.19 0.008565 9.656 9.656000
5 2015-01-12 9.29 9.22 9.40 9.05 -0.021231 9.500 9.583333
6 2015-01-13 9.15 9.17 9.30 9.12 -0.005423 9.364 9.524286
7 2015-01-14 9.23 9.25 9.49 9.18 0.008724 9.280 9.490000
8 2015-01-15 9.27 9.58 9.58 9.19 0.035676 9.328 9.500000
9 2015-01-16 9.62 9.60 9.75 9.48 0.002088 9.364 9.510000

2.2 产生交易信号:即找出买入与卖出信号

# 找买入信号
# 当天的短期均线大于等于长期均线
condition1 = (df['ma_short'] >= df['ma_long'])
# 上一个交易日的短期均线小于长期均线
condition2 = (df['ma_short'].shift(1) < df['ma_long'].shift(1))
# 将买入信号当天的signal设为1
df.loc[condition1 & condition2, 'signal'] = 1

# 找卖出信号
# 当天的短期均线小于等于长期均线
condition1 = (df['ma_short'] <= df['ma_long'])
# 上一个交易日的短期均线大于长期均线
condition2 = (df['ma_short'].shift(1) > df['ma_long'].shift(1))
# 将买入信号当天的signal设为0
df.loc[condition1 & condition2,'signal'] = 0

# 浏览产生交易信号的日期
df[df['signal'].notnull()].head(10)
date open close high low change_pct ma_short ma_long signal
32 2015-02-25 8.77 8.62 8.77 8.59 -0.013730 8.682 8.6810 1.0
38 2015-03-05 8.43 8.35 8.45 8.30 -0.014168 8.562 8.6075 0.0
43 2015-03-12 8.87 9.12 9.34 8.79 0.054335 8.720 8.6330 1.0
80 2015-05-06 11.96 11.88 12.32 11.71 -0.009174 12.314 12.3445 0.0
94 2015-05-26 12.57 12.56 12.62 12.31 0.000797 12.250 12.1190 1.0
99 2015-06-02 12.01 11.92 12.02 11.73 -0.008319 11.940 11.9885 0.0
102 2015-06-05 12.62 12.32 12.70 12.12 -0.004042 12.120 12.0365 1.0
110 2015-06-17 11.97 11.89 12.01 11.68 0.005922 12.136 12.2450 0.0
176 2015-09-22 8.19 8.28 8.36 8.18 0.012225 8.202 8.1830 1.0
177 2015-09-23 8.21 8.09 8.25 8.07 -0.022947 8.172 8.1965 0.0

2.3 由交易信号计算每天股票仓位

我们用1表示满仓,0表示空仓。当出现买入信号之后,全仓买入,当出现卖出信号之后,全仓卖出。

因此,当交易信号signal为1时,第二天仓位position也会变为1,之后的仓位一直为1,直到出现卖出信号,仓位变为0。

注意:此处因为signal的计算运用了收盘价,是每天收盘之后产生的信号,所以仓位position在第二天才会发生改变。
例如2015-05-01产生买入信号,2015-05-02仓位才会变成1,满仓用1表示,空仓用0表示
将signal信号下移一格,表示第二天买入,用1表示满仓,0表示空仓

# 新建列Pos表示仓位,将交易信号下移一格,表示第二天买入,1表示满仓,0表示空仓
df['pos'] = df['signal'].shift()
# 向上填充,将买入之后的pos全部设置为1
df['pos'].fillna(method='ffill', inplace=True)
# 没有买入的pos全部设置为0
df['pos'].fillna(value=0,inplace=True)
# 预览仓位数据
df[(df['date']>='2015-02-24')&(df['date']<'2015-05-06')][['date','signal','pos']].head(20)
date signal pos
32 2015-02-25 1.0 0.0
33 2015-02-26 NaN 1.0
34 2015-02-27 NaN 1.0
35 2015-03-02 NaN 1.0
36 2015-03-03 NaN 1.0
37 2015-03-04 NaN 1.0
38 2015-03-05 0.0 1.0
39 2015-03-06 NaN 0.0
40 2015-03-09 NaN 0.0
41 2015-03-10 NaN 0.0
42 2015-03-11 NaN 0.0
43 2015-03-12 1.0 0.0
44 2015-03-13 NaN 1.0
45 2015-03-16 NaN 1.0
46 2015-03-17 NaN 1.0
47 2015-03-18 NaN 1.0
48 2015-03-19 NaN 1.0
49 2015-03-20 NaN 1.0
50 2015-03-23 NaN 1.0
51 2015-03-24 NaN 1.0

2.4 排除当天开盘就涨跌停导致无法交易的情况,此时仓位不能改变

注意:开盘涨跌停,是基本无法买入或者卖出股票的。

# 找出开盘涨停的日期:即今天的开盘价相对于昨天的收盘价上涨了9.7%以上,此处不用10%是因为由于4舍5入,涨停不一定就是10%
cond_cannot_buy = df['open'] > df['close'].shift(1) * 1.097

# 将开盘涨停且当前position为1时的'pos'设为空值
df.loc[cond_cannot_buy & (df['pos'] == 1),'pos'] = None

# 找出开盘跌停的日期,即今天的开盘价相对于昨天的收盘价跌了9.7%(1-0.097=0.903)
cond_cannot_sell = df['open'] < df['close'].shift(1) *0.903

# 将开盘跌停且当前position为0时的'pos'设为空值
df.loc[cond_cannot_sell & (df['pos'] == 0),'pos'] = None

# position为空的日期表示不能买卖。position仓位只能和前一个交易日保持一致
df['pos'].fillna(method='ffill', inplace=True)

2.5 计算资金曲线并绘图

# 资金曲线:假设起始资金为100万元的每天资金变化情况
# 首先计算资金曲线每天的涨跌幅,‘equity_change’表示资金每天的涨跌幅
# 当天空仓时,pos为0,资产涨幅为0
# 当天满仓时,pos为1,资产涨幅为股票本身的涨跌幅
df['equity_change'] = df['change_pct'] * df['pos']
# 根据每天的涨跌幅计算资金曲线
df['equity_curve'] = 1000000 * (df['equity_change'] + 1).cumprod()
df = df[['date' , 'change_pct','pos','equity_change','equity_curve']]
df.reset_index(inplace=True, drop=True)  # 重置索引,让他从0开始
df.head(10)
date change_pct pos equity_change equity_curve
0 2015-01-05 NaN 0.0 NaN NaN
1 2015-01-06 -0.015000 0.0 -0.0 1000000.0
2 2015-01-07 -0.018274 0.0 -0.0 1000000.0
3 2015-01-08 -0.034126 0.0 -0.0 1000000.0
4 2015-01-09 0.008565 0.0 0.0 1000000.0
5 2015-01-12 -0.021231 0.0 -0.0 1000000.0
6 2015-01-13 -0.005423 0.0 -0.0 1000000.0
7 2015-01-14 0.008724 0.0 0.0 1000000.0
8 2015-01-15 0.035676 0.0 0.0 1000000.0
9 2015-01-16 0.002088 0.0 0.0 1000000.0
# 绘制资金曲线
plt.plot(df['equity_curve'])
plt.show()

请添加图片描述

通过资金曲线我们可以看到,该只股票从2015年1月开始至今,通过该策略最高是1.8倍收益,后面又跌回去了…

当然这个策略只是在此处搭建框架做演示用的,实际策略参数等都需要根据具体情况进行调整。

3. 总结

本文详细的介绍了通过股票数据及均线策略构建股票回测框架的过程:

  1. 导入数据,计算涨跌幅
  2. 通过均线策略产生交易信号
  3. 通过交易信号计算每天仓位
  4. 排除涨跌停无法进行交易的情况
  5. 通过仓位计算每天资金曲线,回测

对于其他择时交易策略,我们只需更改上述框架中的交易信号产生部分,就可以实现回测啦。

后续待改进的地方,本文在计算资金曲线的过程中,没有考虑交易手续费,买入时的价格与当天涨幅不匹配(当天开盘价买入,资金涨幅与当天股票涨幅不是同等的)等问题,下期会写一个实际操作过程中的资金曲线,将这些问题包含进去。如果有不对的地方,欢迎提出来共同学习交流,谢谢!

如果内容对你有帮助,记得点赞、关注哦!也欢迎小伙伴们和我共同学习交流。

更多干货内容持续更新中…

-------欢迎关注下方我的公众号,共同学习交流,获取更多学习资源。------
请添加图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐