动量策略 - Momentum Strategy
0. 引库
import numpy as np
import pandas as pd
import tushare as ts
import matplotlib.pyplot as plt
import seaborn
plt.style.use('seaborn')
import matplotlib as mpl
%matplotlib inline
mpl.rcParams['font.family'] = 'serif'
import warnings; warnings.simplefilter('ignore')
1. 数据准备 & 回测准备
data = ts.get_k_data('hs300', start = '2010-01-01', end='2017-06-30')[['date','close']]
data.rename(columns={'close': 'price'}, inplace=True)
data.set_index('date', inplace = True)
data.head()
|
price |
date |
|
2010-01-04 |
3535.229 |
2010-01-05 |
3564.038 |
2010-01-06 |
3541.727 |
2010-01-07 |
3471.456 |
2010-01-08 |
3480.130 |
2. 策略开发思路
data['returns'] = np.log(data['price'] / data['price'].shift(1))
data.head()
|
price |
returns |
date |
|
|
2010-01-04 |
3535.229 |
NaN |
2010-01-05 |
3564.038 |
0.008116 |
2010-01-06 |
3541.727 |
-0.006280 |
2010-01-07 |
3471.456 |
-0.020040 |
2010-01-08 |
3480.130 |
0.002496 |
data['position'] = np.sign(data['returns'])
data.head()
|
price |
returns |
position |
date |
|
|
|
2010-01-04 |
3535.229 |
NaN |
NaN |
2010-01-05 |
3564.038 |
0.008116 |
1.0 |
2010-01-06 |
3541.727 |
-0.006280 |
-1.0 |
2010-01-07 |
3471.456 |
-0.020040 |
-1.0 |
2010-01-08 |
3480.130 |
0.002496 |
1.0 |
data['strategy'] = data['position'].shift(1) * data['returns']
data.head(10)
|
price |
returns |
position |
strategy |
date |
|
|
|
|
2010-01-04 |
3535.229 |
NaN |
NaN |
NaN |
2010-01-05 |
3564.038 |
0.008116 |
1.0 |
NaN |
2010-01-06 |
3541.727 |
-0.006280 |
-1.0 |
-0.006280 |
2010-01-07 |
3471.456 |
-0.020040 |
-1.0 |
0.020040 |
2010-01-08 |
3480.130 |
0.002496 |
1.0 |
-0.002496 |
2010-01-11 |
3482.052 |
0.000552 |
1.0 |
0.000552 |
2010-01-12 |
3534.916 |
0.015068 |
1.0 |
0.015068 |
2010-01-13 |
3421.144 |
-0.032715 |
-1.0 |
-0.032715 |
2010-01-14 |
3469.051 |
0.013906 |
1.0 |
-0.013906 |
2010-01-15 |
3482.738 |
0.003938 |
1.0 |
0.003938 |
3. 策略可视化
data[['returns', 'strategy']].cumsum().apply(np.exp).plot(figsize=(10, 6));
4. 策略优化之思路——参数优化和穷举
data['position_5'] = np.sign(data['returns'].rolling(5).mean())
data['strategy_5'] = data['position_5'].shift(1) * data['returns']
data[['returns', 'strategy_5']].dropna().cumsum().apply(np.exp).plot(figsize=(10, 6))
参数寻优——使用离散Return计算方法
data['returns_dis'] = data['price'] / data['price'].shift(1) - 1
data['returns_dis_cum'] = (data['returns_dis'] + 1).cumprod()
data.head()
|
price |
returns |
position |
strategy |
position_5 |
strategy_5 |
returns_dis |
returns_dis_cum |
date |
|
|
|
|
|
|
|
|
2010-01-04 |
3535.229 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
2010-01-05 |
3564.038 |
0.008116 |
1.0 |
NaN |
NaN |
NaN |
0.008149 |
1.008149 |
2010-01-06 |
3541.727 |
-0.006280 |
-1.0 |
-0.006280 |
NaN |
NaN |
-0.006260 |
1.001838 |
2010-01-07 |
3471.456 |
-0.020040 |
-1.0 |
0.020040 |
NaN |
NaN |
-0.019841 |
0.981961 |
2010-01-08 |
3480.130 |
0.002496 |
1.0 |
-0.002496 |
NaN |
NaN |
0.002499 |
0.984414 |
price_plot = ['returns_dis_cum']
type(price_plot)
list
for days in [10,20,30,60]:
price_plot.append('sty_cumr_%dd' % days)
data['position_%dd' % days] = np.where(data['returns'].rolling(days).mean()>0, 1, -1)
data['strategy_%dd' % days] = data['position_%dd' % days].shift(1) * data['returns']
data['sty_cumr_%dd' % days] = (data['strategy_%dd' % days] + 1).cumprod()
data.head()
|
price |
returns |
position |
strategy |
position_5 |
strategy_5 |
returns_dis |
returns_dis_cum |
position_10d |
strategy_10d |
sty_cumr_10d |
position_20d |
strategy_20d |
sty_cumr_20d |
position_30d |
strategy_30d |
sty_cumr_30d |
position_60d |
strategy_60d |
sty_cumr_60d |
date |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-01-04 |
3535.229 |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
NaN |
-1 |
NaN |
NaN |
-1 |
NaN |
NaN |
-1 |
NaN |
NaN |
-1 |
NaN |
NaN |
2010-01-05 |
3564.038 |
0.008116 |
1.0 |
NaN |
NaN |
NaN |
0.008149 |
1.008149 |
-1 |
-0.008116 |
0.991884 |
-1 |
-0.008116 |
0.991884 |
-1 |
-0.008116 |
0.991884 |
-1 |
-0.008116 |
0.991884 |
2010-01-06 |
3541.727 |
-0.006280 |
-1.0 |
-0.006280 |
NaN |
NaN |
-0.006260 |
1.001838 |
-1 |
0.006280 |
0.998113 |
-1 |
0.006280 |
0.998113 |
-1 |
0.006280 |
0.998113 |
-1 |
0.006280 |
0.998113 |
2010-01-07 |
3471.456 |
-0.020040 |
-1.0 |
0.020040 |
NaN |
NaN |
-0.019841 |
0.981961 |
-1 |
0.020040 |
1.018115 |
-1 |
0.020040 |
1.018115 |
-1 |
0.020040 |
1.018115 |
-1 |
0.020040 |
1.018115 |
2010-01-08 |
3480.130 |
0.002496 |
1.0 |
-0.002496 |
NaN |
NaN |
0.002499 |
0.984414 |
-1 |
-0.002496 |
1.015574 |
-1 |
-0.002496 |
1.015574 |
-1 |
-0.002496 |
1.015574 |
-1 |
-0.002496 |
1.015574 |
price_plot
['returns_dis_cum',
'sty_cumr_10d',
'sty_cumr_20d',
'sty_cumr_30d',
'sty_cumr_60d']
data[price_plot].dropna().plot(
title='HS300 Multi Parameters Momuntum Strategy',
figsize=(10, 6), style=['--', '--', '--', '--','--'])
5. 策略优化思路之—— High Frequency Data用于Momentum策略
hs300_hf = ts.get_k_data('hs300', ktype='5')
hs300_hf.head(10)
|
date |
open |
close |
high |
low |
volume |
amount |
turnoverratio |
code |
0 |
2019-03-28 14:55 |
3724.82 |
3725.07 |
3725.68 |
3724.42 |
3806742.0 |
{} |
0.0000 |
hs300 |
1 |
2019-03-28 15:00 |
3725.07 |
3728.40 |
3728.40 |
3724.90 |
2450800.0 |
{} |
0.0000 |
hs300 |
2 |
2019-03-29 09:35 |
3739.77 |
3754.43 |
3754.51 |
3739.77 |
10164739.0 |
{} |
0.0000 |
hs300 |
3 |
2019-03-29 09:40 |
3754.44 |
3763.63 |
3763.94 |
3748.90 |
6341654.0 |
{} |
0.0000 |
hs300 |
4 |
2019-03-29 09:45 |
3764.66 |
3763.26 |
3764.84 |
3759.35 |
5622416.0 |
{} |
0.0000 |
hs300 |
5 |
2019-03-29 09:50 |
3763.44 |
3762.66 |
3763.89 |
3754.12 |
5955572.0 |
{} |
0.0000 |
hs300 |
6 |
2019-03-29 09:55 |
3762.87 |
3766.84 |
3769.46 |
3762.87 |
4600542.0 |
{} |
0.0000 |
hs300 |
7 |
2019-03-29 10:00 |
3766.66 |
3758.50 |
3766.66 |
3757.00 |
5533989.0 |
{} |
0.0000 |
hs300 |
8 |
2019-03-29 10:05 |
3758.20 |
3765.39 |
3765.39 |
3758.20 |
3587029.0 |
{} |
0.0000 |
hs300 |
9 |
2019-03-29 10:10 |
3766.19 |
3775.06 |
3775.15 |
3766.19 |
3993704.0 |
{} |
0.0000 |
hs300 |
hs300_hf.set_index('date',inplace = True)
hs300_hf.index = hs300_hf.index.to_datetime()
hs300_hf.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 482 entries, 2019-03-28 14:55:00 to 2019-04-12 15:00:00
Data columns (total 8 columns):
open 482 non-null float64
close 482 non-null float64
high 482 non-null float64
low 482 non-null float64
volume 482 non-null float64
amount 482 non-null object
turnoverratio 482 non-null object
code 482 non-null object
dtypes: float64(5), object(3)
memory usage: 33.9+ KB
hs300_hf['2019-04-01':'2019-04-02'].head()
|
open |
close |
high |
low |
volume |
amount |
turnoverratio |
code |
2019-04-01 09:35:00 |
3901.17 |
3923.72 |
3929.06 |
3901.17 |
25618175.0 |
{} |
0.0000 |
hs300 |
2019-04-01 09:40:00 |
3923.72 |
3952.36 |
3952.74 |
3921.96 |
14481548.0 |
{} |
0.0000 |
hs300 |
2019-04-01 09:45:00 |
3952.36 |
3952.94 |
3955.06 |
3951.27 |
13020862.0 |
{} |
0.0000 |
hs300 |
2019-04-01 09:50:00 |
3952.94 |
3965.55 |
3967.12 |
3952.94 |
11968217.0 |
{} |
0.0000 |
hs300 |
2019-04-01 09:55:00 |
3965.55 |
3953.47 |
3965.55 |
3951.80 |
9892385.0 |
{} |
0.0000 |
hs300 |
hs300_hf['returns'] = np.log(hs300_hf['close'] / hs300_hf['close'].shift(1))
hs300_hf['position'] = np.sign(hs300_hf['returns'].rolling(10).mean())
hs300_hf['strategy'] = hs300_hf['position'].shift(1) * hs300_hf['returns']
hs300_hf[['returns', 'strategy']].dropna().cumsum().apply(np.exp).plot(figsize=(10, 6), style=['--', '--'])