版本号:v2.1
本次迭代,添加了自动评估三均线最优组合功能。
这里的最优组合是按近一段时间,收益最大的最优组合为结果。
经过回测,整体比第一个版本提升了4.1%的收益。
后期将计划添加以收益加权最优组合计算均线组合,并添加自动选股功能,请期待!
策略说明:
所谓的三进兵,是指三条EMA均线组合的策略
交易原则:
系统由三条EMA均线组合而成,分别为小均线、中均线、大均线
当小均线金叉大均线、并且中均线位于大均线下方时,买入
当小均线死叉中均线,并且中均线位于大均线上方时,卖出
止损:当买入后,如果收盘价跌破中均线,止损
选股:本策略里没有做自动选股,而是手动挑选了一些
在研究里做了更多股票的研究,发现本策略并不是适合所有的股票的
所以,各位宽友如果有想法,可在此基础上做迭代,并分享出来,展示你的才华
import numpy as np
import pandas as pd
import datetime
from jqdata import *
from jqlib.technical_analysis import *
# 记录成交历史
trade_history = {}
# 指定股票池,这里默认选择了创业板
stocks_pool = get_index_stocks('399006.XSHE')
# 获取ma_min值
def get_ma(stock, ma_value, end_dt):
price = get_price(security=stock,
end_date=end_dt,
frequency='daily',
fields=['close'],
skip_paused=False,
fq='pre',
count=ma_value+10)['close']
ma = price[-ma_value:].mean()
return ma
# 获取ema值
def get_ema(stock, ma_value, end_dt):
ema = EMA(stock, check_date=end_dt, timeperiod=ma_value)
return ema[stock]
# 判断是否出现买入信息
def is_buy(stock, yesterday, before_yesterday, *ema):
ma_min = ema[0]
ma_med = ema[1]
ma_max = ema[2]
# 求出上一个交易日的ma_min,ma_med,ma_max的值
y_ma_min_value = get_ema(stock, ma_min, yesterday)
y_ma_med_value = get_ema(stock, ma_med, yesterday)
y_ma_max_value = get_ema(stock, ma_max, yesterday)
# 求出上上个交易日的ma_min,ma_med,ma_max的值
by_ma_min_value = get_ema(stock, ma_min, before_yesterday)
by_ma_med_value = get_ema(stock, ma_med, before_yesterday)
by_ma_max_value = get_ema(stock, ma_max, before_yesterday)
if (y_ma_min_value > y_ma_max_value) and (by_ma_min_value < by_ma_max_value) and (y_ma_med_value < y_ma_max_value):
return True
else:
return False
# 判断是否有卖出信息
def is_sell(stock, yesterday, before_yesterday, *ema):
ma_min = ema[0]
ma_med = ema[1]
ma_max = ema[2]
# 求出上一个交易日的ma_min,ma_med,ma_max的值
y_ma_min_value = get_ema(stock, ma_min, yesterday)
y_ma_med_value = get_ema(stock, ma_med, yesterday)
y_ma_max_value = get_ema(stock, ma_max, yesterday)
# 求出上上个交易日的ma_min,ma_med,ma_max的值
by_ma_min_value = get_ema(stock, ma_min, before_yesterday)
by_ma_med_value = get_ema(stock, ma_med, before_yesterday)
by_ma_max_value = get_ema(stock, ma_max, before_yesterday)
if (y_ma_min_value > y_ma_med_value) and (by_ma_min_value < by_ma_med_value) and (y_ma_med_value > y_ma_max_value):
return True
else:
return False
# 判断是否有卖出信息
def is_loss(stock, yesterday, *ema):
ma_min = ema[0]
ma_med = ema[1]
ma_max = ema[2]
# 昨日收盘价
close = get_price(security=stock,
end_date=yesterday,
frequency='daily',
fields=['open','close'],
skip_paused=False,
fq='pre',
count=10)['close'][-1]
# 求出上一个交易日的ma_min,ma_med,ma_max的值
y_ma_min_value = get_ema(stock, ma_min, yesterday)
y_ma_med_value = get_ema(stock, ma_med, yesterday)
y_ma_max_value = get_ema(stock, ma_max, yesterday)
if (close < y_ma_med_value) and (y_ma_med_value < y_ma_max_value):
return True
else:
return False
# 过滤掉有止影线和小实体阳线的时刻
def is_high_line(stock, end_dt):
price = get_price(security=stock,
end_date=end_dt,
frequency='daily',
fields=['open', 'close', 'high', 'low', 'volume', 'money'],
skip_paused=False,
fq='pre',
count=1)
open = price['open'][0]
close = price['close'][0]
high = price['high'][0]
low = price['low'][0]
o_c_ratio = (open-close)/close
h_c_ratio = (high-close)/close
if o_c_ratio > 0 and h_c_ratio < 0.02:
return True
else:
return False
def trade(stock,ma_min,ma_med,ma_max,trade_days):
# 记录盈利
wine_loss_history = []
# 持仓
hold_list = {}
# 权重
weight = 0
# 总权重
last_weihgt = len(trade_days)
# 因为回测的是历史
for trade_day in trade_days:
# 权重累加
weight += 1
# 将要被使用的日期集合
the_days = get_trade_days(end_date=trade_day, count=5)
# 回测当天
today = the_days[-1]
# 上一个交易日
yesterday = the_days[-2]
# 上上个交易日
before_yesterday = the_days[-4]
# ========================卖出操作========================
sell_list = []
for stock,info in hold_list.items():
trade_day = info[0]
buy_price = info[1]
# 判断是否有卖出信号
re_value = is_sell(stock, yesterday, before_yesterday, ma_min,ma_med,ma_max)
# 判断是否触发止损信号
loss = is_loss(stock, yesterday, ma_min,ma_med,ma_max)
# 进行卖出操作
if re_value or loss:
sell_list.append(stock)
price = get_price(security=stock,
end_date=today, # 现实中成交按当天的开盘价交易
frequency='daily',
fields=['open','close'],
skip_paused=False,
fq='pre',
count=10)['open'][-1]
# 进行记录
trade_dic = {'stock':stock,
'buy_date':trade_day,
'buy_price':buy_price,
'sell_date':today,
'sell_price':price,
'ratio':(price-buy_price)/buy_price,
'MA':(ma_min,ma_med,ma_max),
'weight':weight,
'count':last_weihgt}
wine_loss_history.append(trade_dic)
# 从持仓中删除已经卖出的股票
for stock in sell_list:
del hold_list[stock]
# ========================卖出操作========================
# ========================买入操作========================
# 判断是否有买入信号
re_value = is_buy(stock, yesterday, before_yesterday, ma_min,ma_med,ma_max)
# 如果有买入信号,则买入
if re_value:
# 在今天的开盘时买入,参考的买入价格是昨天的收盘价
price = get_price(security=stock,
end_date=today, # 现实中按当天的开盘价交易
frequency='daily',
fields=['open','close'],
skip_paused=False,
fq='pre',
count=10)['open'][-1]
hold_list[stock] = [today, price]
# ========================买入操作========================
#========================将最后一次未卖出的也记录==========================
if stock in hold_list.keys():
sell_list.append(stock)
price = get_price(security=stock,
end_date=today, # 现实中成交按当天的开盘价交易
frequency='daily',
fields=['open','close'],
skip_paused=False,
fq='pre',
count=10)['open'][-1]
# 进行记录
trade_dic = {'stock':stock,
'buy_date':trade_day,
'buy_price':buy_price,
'sell_date':today,
'sell_price':price,
'ratio':(price-buy_price)/buy_price,
'MA':(ma_min,ma_med,ma_max),
'weight':weight,
'count':last_weihgt}
wine_loss_history.append(trade_dic)
#========================将最后一次未卖出的也记录==========================
# 返回交易记录
return wine_loss_history
# 获利近三年的交易日期
days = get_trade_days(end_date=datetime.datetime.now(), count=250*2+100)
train_days = days[:-100]
trade_days = days[-100:]
s_time = datetime.datetime.now()
# 回测股票
trade_stock = '600600.XSHG'
# 保存回测记录
all_list = []
# 三进兵的三个值集合
ma_min1 = [3, 5, 7]
ma_med1 = [10, 20, 30]
ma_max1 = [40, 50, 60]
# 回测不同的三进兵组合
for ma1 in ma_min1:
for ma2 in ma_med1:
for ma3 in ma_max1:
re_dic = trade(trade_stock,ma1,ma2,ma3,train_days)
all_list = all_list + re_dic
# 输出各三进兵组合的值
df = pd.DataFrame(all_list,columns=['stock',
'buy_date',
'buy_price',
'sell_date',
'sell_price',
'ratio',
'MA',
'weight',
'count'])
e_time = datetime.datetime.now()
print(e_time-s_time)
df
0:02:39.866698
stock | buy_date | buy_price | sell_date | sell_price | ratio | MA | weight | count | |
---|---|---|---|---|---|---|---|---|---|
0 | 600600.XSHG | 2016-11-30 | 30.67 | 2016-12-01 | 30.24 | -0.014020 | (3, 10, 40) | 83 | 500 |
1 | 600600.XSHG | 2017-01-24 | 30.06 | 2017-03-13 | 32.65 | 0.086161 | (3, 10, 40) | 149 | 500 |
2 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (3, 10, 40) | 199 | 500 |
3 | 600600.XSHG | 2017-08-02 | 32.94 | 2017-08-03 | 32.36 | -0.017608 | (3, 10, 40) | 247 | 500 |
4 | 600600.XSHG | 2017-10-17 | 32.23 | 2017-11-28 | 30.82 | -0.043748 | (3, 10, 40) | 325 | 500 |
5 | 600600.XSHG | 2017-12-14 | 34.23 | 2018-02-09 | 36.00 | 0.051709 | (3, 10, 40) | 377 | 500 |
6 | 600600.XSHG | 2018-07-20 | 46.11 | 2018-07-30 | 45.02 | -0.023639 | (3, 10, 40) | 488 | 500 |
7 | 600600.XSHG | 2016-11-30 | 30.67 | 2016-12-01 | 30.24 | -0.014020 | (3, 10, 50) | 83 | 500 |
8 | 600600.XSHG | 2017-01-25 | 30.06 | 2017-03-13 | 32.65 | 0.086161 | (3, 10, 50) | 149 | 500 |
9 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (3, 10, 50) | 199 | 500 |
10 | 600600.XSHG | 2017-08-02 | 32.94 | 2017-08-03 | 32.36 | -0.017608 | (3, 10, 50) | 247 | 500 |
11 | 600600.XSHG | 2017-10-17 | 32.23 | 2017-11-28 | 30.82 | -0.043748 | (3, 10, 50) | 325 | 500 |
12 | 600600.XSHG | 2017-12-14 | 34.23 | 2018-02-13 | 36.63 | 0.070114 | (3, 10, 50) | 379 | 500 |
13 | 600600.XSHG | 2018-02-27 | 38.52 | 2018-02-28 | 37.40 | -0.029076 | (3, 10, 50) | 385 | 500 |
14 | 600600.XSHG | 2018-02-28 | 37.40 | 2018-03-01 | 37.35 | -0.001337 | (3, 10, 50) | 386 | 500 |
15 | 600600.XSHG | 2018-07-20 | 46.11 | 2018-07-31 | 44.81 | -0.028193 | (3, 10, 50) | 489 | 500 |
16 | 600600.XSHG | 2017-01-25 | 30.06 | 2017-03-13 | 32.65 | 0.086161 | (3, 10, 60) | 149 | 500 |
17 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (3, 10, 60) | 199 | 500 |
18 | 600600.XSHG | 2017-08-02 | 32.94 | 2017-08-03 | 32.36 | -0.017608 | (3, 10, 60) | 247 | 500 |
19 | 600600.XSHG | 2017-10-17 | 32.23 | 2017-11-28 | 30.82 | -0.043748 | (3, 10, 60) | 325 | 500 |
20 | 600600.XSHG | 2017-12-14 | 34.23 | 2018-02-26 | 38.32 | 0.119486 | (3, 10, 60) | 383 | 500 |
21 | 600600.XSHG | 2018-03-12 | 39.27 | 2018-03-13 | 40.36 | 0.027757 | (3, 10, 60) | 394 | 500 |
22 | 600600.XSHG | 2018-07-17 | 45.46 | 2018-07-31 | 44.81 | -0.014298 | (3, 10, 60) | 489 | 500 |
23 | 600600.XSHG | 2017-01-24 | 30.06 | 2017-03-22 | 32.12 | 0.068530 | (3, 20, 40) | 156 | 500 |
24 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (3, 20, 40) | 199 | 500 |
25 | 600600.XSHG | 2017-05-31 | 33.15 | 2017-07-19 | 31.72 | -0.043137 | (3, 20, 40) | 236 | 500 |
26 | 600600.XSHG | 2017-08-01 | 32.99 | 2017-08-04 | 32.23 | -0.023037 | (3, 20, 40) | 248 | 500 |
27 | 600600.XSHG | 2017-10-17 | 32.23 | 2017-11-30 | 30.56 | -0.051815 | (3, 20, 40) | 327 | 500 |
28 | 600600.XSHG | 2017-12-15 | 35.03 | 2018-03-13 | 40.36 | 0.152155 | (3, 20, 40) | 394 | 500 |
29 | 600600.XSHG | 2018-07-17 | 45.46 | 2018-07-18 | 45.17 | -0.006379 | (3, 20, 40) | 480 | 500 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
114 | 600600.XSHG | 2017-10-19 | 31.75 | 2017-11-28 | 30.82 | -0.029291 | (7, 10, 50) | 325 | 500 |
115 | 600600.XSHG | 2018-07-20 | 46.11 | 2018-07-31 | 44.81 | -0.028193 | (7, 10, 50) | 489 | 500 |
116 | 600600.XSHG | 2017-01-25 | 30.06 | 2017-03-13 | 32.65 | 0.086161 | (7, 10, 60) | 149 | 500 |
117 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (7, 10, 60) | 199 | 500 |
118 | 600600.XSHG | 2018-07-19 | 47.25 | 2018-07-31 | 44.81 | -0.051640 | (7, 10, 60) | 489 | 500 |
119 | 600600.XSHG | 2017-01-25 | 30.06 | 2017-04-24 | 32.84 | 0.092482 | (7, 20, 40) | 177 | 500 |
120 | 600600.XSHG | 2017-05-31 | 33.15 | 2017-07-19 | 31.72 | -0.043137 | (7, 20, 40) | 236 | 500 |
121 | 600600.XSHG | 2017-10-18 | 31.80 | 2017-11-30 | 30.56 | -0.038994 | (7, 20, 40) | 327 | 500 |
122 | 600600.XSHG | 2017-12-15 | 35.03 | 2018-03-13 | 40.36 | 0.152155 | (7, 20, 40) | 394 | 500 |
123 | 600600.XSHG | 2018-07-19 | 47.25 | 2018-07-20 | 46.11 | -0.024127 | (7, 20, 40) | 482 | 500 |
124 | 600600.XSHG | 2017-01-26 | 30.34 | 2017-04-24 | 32.84 | 0.082399 | (7, 20, 50) | 177 | 500 |
125 | 600600.XSHG | 2017-10-20 | 32.11 | 2017-11-30 | 30.56 | -0.048272 | (7, 20, 50) | 327 | 500 |
126 | 600600.XSHG | 2017-12-15 | 35.03 | 2018-03-13 | 40.36 | 0.152155 | (7, 20, 50) | 394 | 500 |
127 | 600600.XSHG | 2018-07-20 | 46.11 | 2018-08-01 | 43.88 | -0.048363 | (7, 20, 50) | 490 | 500 |
128 | 600600.XSHG | 2017-01-26 | 30.34 | 2017-04-24 | 32.84 | 0.082399 | (7, 20, 60) | 177 | 500 |
129 | 600600.XSHG | 2017-05-24 | 31.63 | 2017-05-25 | 31.08 | -0.017389 | (7, 20, 60) | 199 | 500 |
130 | 600600.XSHG | 2017-10-23 | 31.77 | 2017-11-30 | 30.56 | -0.038086 | (7, 20, 60) | 327 | 500 |
131 | 600600.XSHG | 2017-12-15 | 35.03 | 2018-03-13 | 40.36 | 0.152155 | (7, 20, 60) | 394 | 500 |
132 | 600600.XSHG | 2017-01-25 | 30.06 | 2017-04-24 | 32.84 | 0.092482 | (7, 30, 40) | 177 | 500 |
133 | 600600.XSHG | 2017-05-31 | 33.15 | 2017-07-19 | 31.72 | -0.043137 | (7, 30, 40) | 236 | 500 |
134 | 600600.XSHG | 2017-10-18 | 31.80 | 2017-12-01 | 30.38 | -0.044654 | (7, 30, 40) | 328 | 500 |
135 | 600600.XSHG | 2017-12-18 | 35.30 | 2018-02-22 | 37.96 | 0.075354 | (7, 30, 40) | 381 | 500 |
136 | 600600.XSHG | 2018-07-19 | 47.25 | 2018-07-20 | 46.11 | -0.024127 | (7, 30, 40) | 482 | 500 |
137 | 600600.XSHG | 2017-01-26 | 30.34 | 2017-04-24 | 32.84 | 0.082399 | (7, 30, 50) | 177 | 500 |
138 | 600600.XSHG | 2017-10-20 | 32.11 | 2017-12-01 | 30.38 | -0.053877 | (7, 30, 50) | 328 | 500 |
139 | 600600.XSHG | 2017-12-18 | 35.30 | 2018-03-13 | 40.36 | 0.143343 | (7, 30, 50) | 394 | 500 |
140 | 600600.XSHG | 2018-07-20 | 46.11 | 2018-08-07 | 38.08 | -0.174149 | (7, 30, 50) | 494 | 500 |
141 | 600600.XSHG | 2017-01-26 | 30.34 | 2017-04-24 | 32.84 | 0.082399 | (7, 30, 60) | 177 | 500 |
142 | 600600.XSHG | 2017-10-23 | 31.77 | 2017-12-01 | 30.38 | -0.043752 | (7, 30, 60) | 328 | 500 |
143 | 600600.XSHG | 2017-12-18 | 35.30 | 2018-03-13 | 40.36 | 0.143343 | (7, 30, 60) | 394 | 500 |
144 rows × 9 columns
group = df.groupby(by=['MA'])
print('组合收益之和')
max_sum = group.sum()
max_sum = max_sum.sort_values(by=['ratio'],ascending=False).loc[:,['ratio']].head()
value = {'stock':trade_stock, 'MA':max_sum.index[0]}
print(value)
max_sum
组合收益之和 {'MA': (7, 30, 60), 'stock': '600600.XSHG'}
ratio | |
---|---|
MA | |
(7, 30, 60) | 0.181990 |
(7, 20, 60) | 0.179080 |
(3, 30, 60) | 0.171834 |
(5, 30, 60) | 0.168070 |
(5, 20, 60) | 0.164303 |
print('各组合总收益对比图')
import matplotlib.pylab as plt
max_sum.T.plot(kind='bar')
plt.show()
各组合总收益对比图
print('组合收益标准差')
min_std = group.std()
min_std = min_std.loc[max_sum.index,['ratio']]
min_std = min_std.sort_values(by=['ratio'])
value = {'stock':trade_stock, 'MA':min_std.index[0]}
print(value)
min_std
组合收益标准差 {'MA': (3, 30, 60), 'stock': '600600.XSHG'}
ratio | |
---|---|
MA | |
(3, 30, 60) | 0.050805 |
(5, 20, 60) | 0.087410 |
(7, 20, 60) | 0.088838 |
(5, 30, 60) | 0.090379 |
(7, 30, 60) | 0.095422 |
print('各组合收益拆线图')
for name in max_sum.index:
table = df[df['MA']==name][['ratio']]
x = range(len(table.index))
plt.plot(x,table)
plt.legend(max_sum.index)
plt.show()
各组合收益拆线图
print('各组合收益对比图')
series = []
for name in max_sum.index:
table = df[df['MA']==name]['ratio'].values
series.append(table)
bar_df = pd.DataFrame(series,max_sum.index).T
bar_df.plot(kind='bar')
plt.show()
print('交易次数表')
bar_df.T
各组合收益对比图
交易次数表
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
MA | |||||
(7, 30, 60) | 0.082399 | -0.043752 | 0.143343 | NaN | NaN |
(7, 20, 60) | 0.082399 | -0.017389 | -0.038086 | 0.152155 | NaN |
(3, 30, 60) | 0.073852 | -0.017608 | 0.022339 | 0.099629 | -0.006379 |
(5, 30, 60) | 0.078177 | -0.017608 | -0.044654 | 0.152155 | NaN |
(5, 20, 60) | 0.068530 | -0.017389 | -0.038994 | 0.152155 | NaN |
print('求加权后的表现-平均到每一次交易')
dic = []
for name, g in group:
weight_df = df[df['MA']==name]
w = weight_df['weight']
r = weight_df['ratio']
c = weight_df['weight'].sum()
v = w*r/c
dic.append({'ma':name, 'ratio':sum(v)})
w_df = pd.DataFrame(dic)
w_df = w_df.sort_values(by=['ratio'], ascending=False)
w_df.head()
求加权后的表现-平均到每一次交易
ma | ratio | |
---|---|---|
26 | (7, 30, 60) | 0.063082 |
23 | (7, 20, 60) | 0.053436 |
14 | (5, 20, 60) | 0.050584 |
17 | (5, 30, 60) | 0.047784 |
8 | (3, 30, 60) | 0.031668 |
print('求加权后的表现-平均到每天')
dic = []
count = int(df['count'].mean())
l = list(range(1,count))
for name, g in group:
weight_df = df[df['MA']==name]
w = weight_df['weight']
r = weight_df['ratio']
v = w*r/sum(l)
dic.append({'ma':name, 'ratio':sum(v)})
w_avge_df = pd.DataFrame(dic)
w_avge_df = w_avge_df.sort_values(by=['ratio'], ascending=False)
w_avge_df.head()
result_dic = []
print('最大收益组合回测本年',max_sum.index[0])
ma1,ma2,ma3 = max_sum.index[0]
re_dic = trade(trade_stock,ma1,ma2,ma3,trade_days)
re_dic = pd.DataFrame(re_dic)
print('总盈利', re_dic.sum()['ratio'])
result_dic.append({'收益':re_dic.sum()['ratio']})
re_dic
print('最小标准差组合回测本年',min_std.index[0])
ma1,ma2,ma3 = min_std.index[0]
re_dic = trade(trade_stock,ma1,ma2,ma3,trade_days)
re_dic = pd.DataFrame(re_dic)
print('总盈利', re_dic.sum()['ratio'])
result_dic.append({'收益':re_dic.sum()['ratio']})
re_dic
print('最优加权组合回测本年',w_df['ma'].iloc[0])
ma1,ma2,ma3 = w_df['ma'].iloc[0]
re_dic = trade(trade_stock,ma1,ma2,ma3,trade_days)
re_dic = pd.DataFrame(re_dic)
print('总盈利', re_dic.sum()['ratio'])
result_dic.append({'收益':re_dic.sum()['ratio']})
re_dic
df = pd.DataFrame(result_dic,index=['最大收益组合'+str(max_sum.index[0]),
'最小标准差组合'+str(min_std.index[0]),
'最优加权组合'+str(w_df['ma'].iloc[0])])
df.T.plot(kind='bar')
plt.show()
df
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程