SMA — 移动平均及双均线模型

0. 引库

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn
plt.style.use('seaborn')        
import matplotlib as mpl
mpl.rcParams['font.family'] = 'serif'               # 解决一些字体显示乱码问题
import warnings; warnings.simplefilter('ignore')    # 忽略警告信息
import numpy as np
import pandas as pd
import tushare as ts

1. 前导知识备习

# 采用 Tushare API 获取中信证券 600030 数据
data = ts.get_k_data('600030', start = '2010-01-01', end='2017-06-30') 
data.head()    #DataFrame数据结构
dateopenclosehighlowvolumecode
02010-01-0417.01616.63917.17616.6121106207.58600030
12010-01-0516.77717.45217.60116.3832093915.41600030
22010-01-0617.54817.25017.62817.2291437889.30600030
32010-01-0717.23916.83017.48416.6971235592.34600030
42010-01-0816.71817.15417.20816.6441040929.92600030
data.set_index('date', inplace = True)   #设置索引;替换,真实覆盖;
data.head()
openclosehighlowvolumecode
date
2010-01-0417.01616.63917.17616.6121106207.58600030
2010-01-0516.77717.45217.60116.3832093915.41600030
2010-01-0617.54817.25017.62817.2291437889.30600030
2010-01-0717.23916.83017.48416.6971235592.34600030
2010-01-0816.71817.15417.20816.6441040929.92600030
data['SMA_20'] = data['close'].rolling(20).mean()
data['SMA_60'] = data['close'].rolling(60).mean()
data.tail()
openclosehighlowvolumecodeSMA_20SMA_60
date
2017-06-2616.18616.49216.63516.1482113195.060003015.7970015.467750
2017-06-2716.48216.34916.51116.301924024.060003015.8314015.482400
2017-06-2816.32016.33016.50216.253871050.060003015.8624515.497533
2017-06-2916.32016.33016.42516.224668341.060003015.8968515.513783
2017-06-3016.26316.26316.34916.072751091.060003015.9274515.528283
# 可视化
data[['close','SMA_20','SMA_60']].plot(figsize = (10,6))    

data

#计算股票连续收益率 returns
data['returns'] = np.log(data['close'] / data['close'].shift(1))
# 算股票离散收益率方法1
data['returns_dis'] = data['close']/data['close'].shift(1) - 1
# 算股票连续收益率方法2
data['return_dis2'] = data['close'].pct_change()
data.head()
openclosehighlowvolumecodeSMA_20SMA_60returnsreturns_disreturn_dis2
date
2010-01-0417.01616.63917.17616.6121106207.58600030NaNNaNNaNNaNNaN
2010-01-0516.77717.45217.60116.3832093915.41600030NaNNaN0.0477050.0488610.048861
2010-01-0617.54817.25017.62817.2291437889.30600030NaNNaN-0.011642-0.011575-0.011575
2010-01-0717.23916.83017.48416.6971235592.34600030NaNNaN-0.024649-0.024348-0.024348
2010-01-0816.71817.15417.20816.6441040929.92600030NaNNaN0.0190680.0192510.019251
# 核心判断语句(用于依策略确定决策符号)
data['position'] = np.where(data['SMA_20'] > data['SMA_60'], 1, -1)
# 可视化计算的累计收益率
data['returns'].cumsum().apply(np.exp).plot(figsize=(10, 6));     

累计收益率

SMA策略

1. 数据准备 & 回测准备

import numpy as np
import pandas as pd
import tushare as ts
# 推荐改用 Tushare新的数据获取接口,不然数据获取有bug;
data = ts.get_k_data('hs300', start = '2010-01-01', end='2017-06-30')
# 把 data转换成为 DataFrame格式
data = pd.DataFrame(data) 
data.head()
dateopenclosehighlowvolumecode
02010-01-043592.4683535.2293597.753535.2366101080.0hs300
12010-01-053545.1863564.0383577.533497.6685809641.0hs300
22010-01-063558.7003541.7273588.833541.1778473125.0hs300
32010-01-073543.1603471.4563558.563452.7780350037.0hs300
42010-01-083456.9083480.1303482.083426.7060790253.0hs300
# 用字典对列改名
data.rename(columns={'close': 'price'}, inplace=True)
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1819 entries, 0 to 1818
Data columns (total 7 columns):
date      1819 non-null object
open      1819 non-null float64
price     1819 non-null float64
high      1819 non-null float64
low       1819 non-null float64
volume    1819 non-null float64
code      1819 non-null object
dtypes: float64(5), object(2)
memory usage: 113.7+ KB
data.head()
dateopenpricehighlowvolumecode
02010-01-043592.4683535.2293597.753535.2366101080.0hs300
12010-01-053545.1863564.0383577.533497.6685809641.0hs300
22010-01-063558.7003541.7273588.833541.1778473125.0hs300
32010-01-073543.1603471.4563558.563452.7780350037.0hs300
42010-01-083456.9083480.1303482.083426.7060790253.0hs300
# 设置date项为列,inplace=True进行覆盖操作
data.set_index('date', inplace = True)
data.head()
openpricehighlowvolumecode
date
2010-01-043592.4683535.2293597.753535.2366101080.0hs300
2010-01-053545.1863564.0383577.533497.6685809641.0hs300
2010-01-063558.7003541.7273588.833541.1778473125.0hs300
2010-01-073543.1603471.4563558.563452.7780350037.0hs300
2010-01-083456.9083480.1303482.083426.7060790253.0hs300
data['SMA_10'] = data['price'].rolling(10).mean()
data['SMA_60'] = data['price'].rolling(60).mean()
data.tail()
openpricehighlowvolumecodeSMA_10SMA_60
date
2017-06-263627.023668.093671.943627.02134637995.0hs3003573.4553475.314500
2017-06-273665.583674.723676.533648.7697558702.0hs3003582.7003478.729667
2017-06-283664.163646.173672.193644.0397920858.0hs3003593.7873481.746000
2017-06-293649.253668.833669.133644.7385589498.0hs3003607.7913485.613833
2017-06-303654.733666.803669.763646.2381510028.0hs3003622.5953489.126333
# 选择多列进行绘制
data[['price','SMA_10','SMA_60']].plot(title='HS300 stock price | 10 & 60 days SMAs', figsize=(10, 6));

绘制多列

2. 策略开发思路

data['position'] = np.where(data['SMA_10'] > data['SMA_60'], 1, -1)
data.head()
openpricehighlowvolumecodeSMA_10SMA_60position
date
2010-01-043592.4683535.2293597.753535.2366101080.0hs300NaNNaN-1
2010-01-053545.1863564.0383577.533497.6685809641.0hs300NaNNaN-1
2010-01-063558.7003541.7273588.833541.1778473125.0hs300NaNNaN-1
2010-01-073543.1603471.4563558.563452.7780350037.0hs300NaNNaN-1
2010-01-083456.9083480.1303482.083426.7060790253.0hs300NaNNaN-1
# 去掉空值,NaN
data.dropna(inplace=True)
data['position'].plot(ylim=[-1.1, 1.1], title='Market Positioning');

position

3. 计算策略年化收益并可视化

data['returns'] = np.log(data['price'] / data['price'].shift(1))
data.head()
openpricehighlowvolumecodeSMA_10SMA_60positionreturns
date
2010-04-023400.1393407.3463412.203391.8169073452.0hs3003322.91363313.5381171NaN
2010-04-063422.8493405.1453436.293386.8965191710.0hs3003333.16533311.3700501-0.000646
2010-04-073403.0883386.9493404.583369.0254011228.0hs3003344.30293308.4185671-0.005358
2010-04-083381.3063346.7443381.313336.1662185322.0hs3003351.31043305.1688501-0.011942
2010-04-093348.7733379.1703379.403342.4751280567.0hs3003366.31463303.63075010.009642
# data['returns_dis'] = data['price']/data['price'].shift(1)-1    #离散计算return方法1
# data['return_dis2'] = data['price'].pct_change()                #离散计算return方法2
# 绘制收益率的直方图
data['returns'].hist(bins=35);

直方图

# 注意进行 shift(1),用错一般会使得回测收益高估
data['strategy'] = data['position'].shift(1) * data['returns']
data.head()
openpricehighlowvolumecodeSMA_10SMA_60positionreturnsstrategy
date
2010-04-023400.1393407.3463412.203391.8169073452.0hs3003322.91363313.5381171NaNNaN
2010-04-063422.8493405.1453436.293386.8965191710.0hs3003333.16533311.3700501-0.000646-0.000646
2010-04-073403.0883386.9493404.583369.0254011228.0hs3003344.30293308.4185671-0.005358-0.005358
2010-04-083381.3063346.7443381.313336.1662185322.0hs3003351.31043305.1688501-0.011942-0.011942
2010-04-093348.7733379.1703379.403342.4751280567.0hs3003366.31463303.63075010.0096420.009642
# 算总的收益率
data[['returns', 'strategy']].sum()
returns     0.073386
strategy    0.727122
dtype: float64
# 测试
data[['returns','strategy']].tail()
returnsstrategy
date
2017-06-260.0124020.012402
2017-06-270.0018060.001806
2017-06-28-0.007800-0.007800
2017-06-290.0061960.006196
2017-06-30-0.000553-0.000553
# 测试
data[['returns', 'strategy']].head()
returnsstrategy
date
2010-04-02NaNNaN
2010-04-06-0.000646-0.000646
2010-04-07-0.005358-0.005358
2010-04-08-0.011942-0.011942
2010-04-090.0096420.009642
# 对收益率进行累积求和
data[['returns', 'strategy']].cumsum().tail()
returnsstrategy
date
2017-06-260.0737370.727474
2017-06-270.0755430.729280
2017-06-280.0677440.721480
2017-06-290.0739390.727676
2017-06-300.0733860.727122
data[['returns', 'strategy']].sum()
returns     0.073386
strategy    0.727122
dtype: float64
# 计算累积收益率
data[['returns', 'strategy']].cumsum().apply(np.exp).tail()
returnsstrategy
date
2017-06-261.0765242.069846
2017-06-271.0784702.073587
2017-06-281.0700912.057477
2017-06-291.0767412.070263
2017-06-301.0761452.069118
# 可视化
data[['returns', 'strategy']].cumsum().apply(np.exp).plot(figsize=(10, 6));

可视化

4. 策略收益风险评估

# 计算年化收益率
data[['returns', 'strategy']].mean() * 252
returns     0.010513
strategy    0.104170
dtype: float64
# 计算年化风险
data[['returns', 'strategy']].std() * 252 ** 0.5   
returns     0.245468
strategy    0.245382
dtype: float64
# 计算策略累积收益率
data['cumret'] = data['strategy'].cumsum().apply(np.exp)
data['cumret'].tail()
date
2017-06-26    2.069846
2017-06-27    2.073587
2017-06-28    2.057477
2017-06-29    2.070263
2017-06-30    2.069118
Name: cumret, dtype: float64
# 计算策略累积最大值
data['cummax'] = data['cumret'].cummax()
data['cummax'].head(6)
date
2010-04-02         NaN
2010-04-06    0.999354
2010-04-07    0.999354
2010-04-08    0.999354
2010-04-09    0.999354
2010-04-12    0.999354
Name: cummax, dtype: float64
data.tail()
openpricehighlowvolumecodeSMA_10SMA_60positionreturnsstrategycumretcummax
date
2017-06-263627.023668.093671.943627.02134637995.0hs3003573.4553475.31450010.0124020.0124022.0698462.731778
2017-06-273665.583674.723676.533648.7697558702.0hs3003582.7003478.72966710.0018060.0018062.0735872.731778
2017-06-283664.163646.173672.193644.0397920858.0hs3003593.7873481.7460001-0.007800-0.0078002.0574772.731778
2017-06-293649.253668.833669.133644.7385589498.0hs3003607.7913485.61383310.0061960.0061962.0702632.731778
2017-06-303654.733666.803669.763646.2381510028.0hs3003622.5953489.1263331-0.000553-0.0005532.0691182.731778
# 绘制累积收益率和累积最大值
data[['cumret', 'cummax']].plot(figsize=(10, 6));

累积最大值

# 算回撤序列
drawdown = (data['cummax'] - data['cumret'])
# 算最大回撤
drawdown.max()
0.7744165301748813
# 算所有drawdown==0项
temp = drawdown[drawdown == 0]
temp.head()
date
2010-04-06    0.0
2010-05-06    0.0
2010-05-07    0.0
2010-05-11    0.0
2010-05-17    0.0
dtype: float64
temp.index[1:]
Index(['2010-05-06', '2010-05-07', '2010-05-11', '2010-05-17', '2010-06-07',
       '2010-06-29', '2010-06-30', '2010-07-01', '2010-07-05', '2010-10-15',
       '2010-10-19', '2010-10-20', '2010-10-25', '2010-11-05', '2010-11-08',
       '2014-12-04', '2014-12-05', '2014-12-08', '2014-12-16', '2014-12-17',
       '2014-12-19', '2014-12-22', '2014-12-26', '2014-12-29', '2014-12-30',
       '2014-12-31', '2015-01-05', '2015-01-07', '2015-03-16', '2015-03-17',
       '2015-03-18', '2015-03-20', '2015-03-23', '2015-03-24', '2015-03-30',
       '2015-04-01', '2015-04-02', '2015-04-03', '2015-04-07', '2015-04-08',
       '2015-04-10', '2015-04-13', '2015-04-14', '2015-04-16', '2015-04-17',
       '2015-04-21', '2015-04-22', '2015-04-23', '2015-04-27', '2015-05-21',
       '2015-05-22', '2015-05-25', '2015-05-26', '2015-06-05', '2015-06-08',
       '2015-07-08', '2015-08-21', '2015-08-24', '2015-08-25', '2015-08-26'],
      dtype='object', name='date')
temp.index[:-1]
Index(['2010-04-06', '2010-05-06', '2010-05-07', '2010-05-11', '2010-05-17',
       '2010-06-07', '2010-06-29', '2010-06-30', '2010-07-01', '2010-07-05',
       '2010-10-15', '2010-10-19', '2010-10-20', '2010-10-25', '2010-11-05',
       '2010-11-08', '2014-12-04', '2014-12-05', '2014-12-08', '2014-12-16',
       '2014-12-17', '2014-12-19', '2014-12-22', '2014-12-26', '2014-12-29',
       '2014-12-30', '2014-12-31', '2015-01-05', '2015-01-07', '2015-03-16',
       '2015-03-17', '2015-03-18', '2015-03-20', '2015-03-23', '2015-03-24',
       '2015-03-30', '2015-04-01', '2015-04-02', '2015-04-03', '2015-04-07',
       '2015-04-08', '2015-04-10', '2015-04-13', '2015-04-14', '2015-04-16',
       '2015-04-17', '2015-04-21', '2015-04-22', '2015-04-23', '2015-04-27',
       '2015-05-21', '2015-05-22', '2015-05-25', '2015-05-26', '2015-06-05',
       '2015-06-08', '2015-07-08', '2015-08-21', '2015-08-24', '2015-08-25'],
      dtype='object', name='date')
temp.index[1:].to_datetime() - temp.index[:-1].to_datetime()
TimedeltaIndex([  '30 days',    '1 days',    '4 days',    '6 days',
                  '21 days',   '22 days',    '1 days',    '1 days',
                   '4 days',  '102 days',    '4 days',    '1 days',
                   '5 days',   '11 days',    '3 days', '1487 days',
                   '1 days',    '3 days',    '8 days',    '1 days',
                   '2 days',    '3 days',    '4 days',    '3 days',
                   '1 days',    '1 days',    '5 days',    '2 days',
                  '68 days',    '1 days',    '1 days',    '2 days',
                   '3 days',    '1 days',    '6 days',    '2 days',
                   '1 days',    '1 days',    '4 days',    '1 days',
                   '2 days',    '3 days',    '1 days',    '2 days',
                   '1 days',    '4 days',    '1 days',    '1 days',
                   '4 days',   '24 days',    '1 days',    '3 days',
                   '1 days',   '10 days',    '3 days',   '30 days',
                  '44 days',    '3 days',    '1 days',    '1 days'],
               dtype='timedelta64[ns]', freq=None)
periods = temp.index[1:].to_datetime() - temp.index[:-1].to_datetime()
periods
TimedeltaIndex([  '30 days',    '1 days',    '4 days',    '6 days',
                  '21 days',   '22 days',    '1 days',    '1 days',
                   '4 days',  '102 days',    '4 days',    '1 days',
                   '5 days',   '11 days',    '3 days', '1487 days',
                   '1 days',    '3 days',    '8 days',    '1 days',
                   '2 days',    '3 days',    '4 days',    '3 days',
                   '1 days',    '1 days',    '5 days',    '2 days',
                  '68 days',    '1 days',    '1 days',    '2 days',
                   '3 days',    '1 days',    '6 days',    '2 days',
                   '1 days',    '1 days',    '4 days',    '1 days',
                   '2 days',    '3 days',    '1 days',    '2 days',
                   '1 days',    '4 days',    '1 days',    '1 days',
                   '4 days',   '24 days',    '1 days',    '3 days',
                   '1 days',   '10 days',    '3 days',   '30 days',
                  '44 days',    '3 days',    '1 days',    '1 days'],
               dtype='timedelta64[ns]', freq=None)
# 算持续最长时间
periods.max()
Timedelta('1487 days 00:00:00')

5. 策略优化的一种思路

hs300 = ts.get_k_data('hs300','2010-01-01', '2017-06-30')[['date','close']]
hs300 = pd.DataFrame(hs300)   # 一般不用
hs300.rename(columns={'close': 'price'}, inplace=True) 
hs300.set_index('date',inplace = True)
hs300.head()
price
date
2010-01-043535.229
2010-01-053564.038
2010-01-063541.727
2010-01-073471.456
2010-01-083480.130
hs300['SMA_10'] = hs300['price'].rolling(10).mean()
hs300['SMA_60'] = hs300['price'].rolling(60).mean()
hs300[['price', 'SMA_10', 'SMA_60']].tail()
priceSMA_10SMA_60
date
2017-06-263668.093573.4553475.314500
2017-06-273674.723582.7003478.729667
2017-06-283646.173593.7873481.746000
2017-06-293668.833607.7913485.613833
2017-06-303666.803622.5953489.126333
# 绘图
hs300[['price', 'SMA_10', 'SMA_60']].plot(grid=True, figsize = (8,6));

绘图

# 算10日SMA和60日SMA差值
hs300['10-60'] = hs300['SMA_10'] - hs300['SMA_60']
hs300['10-60'].tail()
date
2017-06-26     98.140500
2017-06-27    103.970333
2017-06-28    112.041000
2017-06-29    122.177167
2017-06-30    133.468667
Name: 10-60, dtype: float64
SD = 20  # 设置阈值                   
hs300['regime'] = np.where(hs300['10-60'] > SD, 1,0)
hs300['regime'] = np.where(hs300['10-60'] < -SD, -1,hs300['regime']) # 重要
hs300['regime'].value_counts()
 1    792
-1    751
 0    276
Name: regime, dtype: int64
hs300.tail(20)
priceSMA_10SMA_6010-60regime
date
2017-06-053468.753457.5423448.4155009.1265000
2017-06-063492.883466.4453449.06416717.3808330
2017-06-073533.873478.7083450.48316728.2248331
2017-06-083560.983492.3873452.71716739.6698331
2017-06-093576.173507.5873455.18850052.3985001
2017-06-123574.393516.4603457.12666759.3333331
2017-06-133582.273526.6443459.21966767.4243331
2017-06-143535.303530.8863460.41400070.4720001
2017-06-153528.793533.9913461.20200072.7890001
2017-06-163518.763537.2163462.41783374.7981671
2017-06-193553.673545.7083464.15216781.5558331
2017-06-203546.493551.0693465.48783385.5811671
2017-06-213587.963556.4783467.78633388.6916671
2017-06-223590.343559.4143469.92566789.4883331
2017-06-233622.883564.0853472.14700091.9380001
2017-06-263668.093573.4553475.31450098.1405001
2017-06-273674.723582.7003478.729667103.9703331
2017-06-283646.173593.7873481.746000112.0410001
2017-06-293668.833607.7913485.613833122.1771671
2017-06-303666.803622.5953489.126333133.4686671
hs300['Market'] = np.log(hs300['price']/hs300['price'].shift(1))
hs300['Strategy'] = hs300['regime'].shift(1) * hs300['Market']
hs300[['Market','Strategy']].cumsum().apply(np.exp).plot(grid=True, figsize = (8,6));

python

hs300.head()
priceSMA_10SMA_6010-60regimeMarketStrategy
date
2010-01-043535.229NaNNaNNaN0NaNNaN
2010-01-053564.038NaNNaNNaN00.0081160.0
2010-01-063541.727NaNNaNNaN0-0.006280-0.0
2010-01-073471.456NaNNaNNaN0-0.020040-0.0
2010-01-083480.130NaNNaNNaN00.0024960.0
# 算总收益
hs300[['Market', 'Strategy']].sum()
Market      0.036541
Strategy    0.896131
dtype: float64
# 算优化策略年化收益
hs300[['Market', 'Strategy']].mean() * 252
Market      0.005065
Strategy    0.124216
dtype: float64
# 算优化策略年化风险
hs300[['Market', 'Strategy']].std() * 252 ** 0.5
Market      0.244318
Strategy    0.235367
dtype: float64
Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐