上一篇文章:[【新手入门教程】多股票追涨杀跌策略][20]
[20]: https://www.joinquant.com/post/6986?tag=algorithm
##MACD均线择时策略##
> 学习内容:
> -学会技术指标择时策略
> -了解使用talib
> -学会大盘择时概念
> -自主完成MACD
> -向导式初体验
###1 确定策略内容###
在之前的教程中,我们学习了如何通过财务指标等对股票进行筛选等操作。今天我们将以MACD为例,探究如何利用**技术指标**进行策略的构建与实现。
**> MACD的组成**
MACD(Moving Average Convergence and Divergence)即**指数平滑移动平均线**,由Geral Appel 于1970年提出,属于*大势趋势类指标*,它由***长期慢速均线DEA,短期快线的DIF,红色能量柱(多头),绿色能量柱(空头)、0轴(多空分界线)***五部分组成,即“两线两柱一轴”组合起来形成。
![gaitubao_com_14957648667277.jpeg][2]
[2]: https://image.joinquant.com/90687b2bb1f54f13d9925a9cb150365a
在图中表示出来则为:
![gaitubao_com_14957632961470.jpg][1]
[1]: https://image.joinquant.com/7585ddcc2aaca211a586069fd8d8ad9a
图片来源:http://mini.eastday.com/a/160224221510636.html
图中水平线为零轴,又称多空分水岭,顾名思义上方为多头势力较强,下方则为空头势力较强。
当短期快线DIF值和长期慢线DEA值上穿零轴,同时零轴之上出现红色柱状体,表示股价趋势当时属于多头市场,投资者可以考虑持股待涨。
DIF值和DEA值下穿零轴,同时零轴之下出现绿色柱状体,则表示股价趋势当时属于空头市场,投资者应当空仓持币,规避风险。
**> MACD思想————均线位置与买卖时机判断**
MACD是从双指数移动平均线发展而来的,由快的指数移动平均线(EMA12)减去慢的指数移动平均线(EMA26)得到快线DIF,再用2×(快线DIF-DEA)得到MACD柱。MACD的意义和双移动平均线相似,即**由快、慢均线的离散、聚合来显示当前的多空状态和股价可能的发展变化趋势**并对**买进、卖出时机**作出研判,但MACD阅读起来更方便。
总体而言:
*当MACD从负数转向正数,即买入信号
当MACD从正数转向负数,即卖出信号
当MACD以大角度变化,表示快的移动平均线和慢的移动平均线的差距非常迅速的拉开,代表了一个市场大趋势的转变*
**> DIF与DEA金叉的使用**
当白线即短期快线DIF向上穿过黄色的长期慢线DEA时,即“**快线高于慢线**”,属于做多金叉信号。然而实际中需要进行**“金叉+零轴”**的综合考虑。
> 当零轴之下出现金叉时,股价属于弱势市场,金叉为弱势金叉,股价极有可能短期弱势反弹或暂时止跌后再度夭折,一般没有做多价值。
> 当零轴之上发生金叉时,股价属于强势市场,金叉为强势金叉,股价后市成功惯性上攻的概率较高。若零轴之上出现二次金叉信号时,又称**零上二次红金叉**,属于股价强势中的强势,此时跟进做多,股价往往容易出现加速上涨。属于短线极佳买点。
![gaitubao_com_14957675709948.jpg][3]
[3]: https://image.joinquant.com/0a468586c89034e757a2050f1bf2347b
**> DIF与DEA死叉的使用**
当白线即短期快线DIF向下穿过黄色的长期慢线DEA时,即“**快线低于慢线**”,属于做空死叉信号。死叉同样需要结合零轴。
> 当零轴之上出现死叉时,一般认为是股价上升趋势途中的短暂回调,后市股价仍有再次走强的可能。
> 当零轴之下发生死叉时,一般认为是股价下跌趋势途中的继续回调,后市股价惯性下跌的概率较高,尤其是MACD零轴之下出现二次死叉时,股价更容易出现加速下跌行情,所以零轴之下,无论出现的是一次死叉或二次死叉,投资者都应当空仓观望,静观其变。
**策略内容**
了解了MACD的基本情况,我们就能够制定本次利用技术指标构建的策略内容。
基本思路:
> - 筛选出符合:
10<市盈率(pe_ratio)<40, 市净率(pb_ratio)<3, 净利润环比增长率(inc_net_profit_annual)>0.3,净资产收益率(roe)>15的股票,按照销售毛利率进行降序排列,选取前50只
> - 买入:DIF从下而上穿过DEA,即形成金叉
> - 卖出:DIF从上往下穿过DEA,即形成死叉
> - 十天一次调仓
翻译成计算机语言即:
1.运用`get_fundalmentals`函数查询相关财务数据并筛选出符合条件的股票
2.运用`talib`读取MACD相关指标数值
3.利用`if`条件语句以及`and`、`or`逻辑语句进行买入卖出条件设置
###2 基本框架构建###
在之前的教程中,我们基本上已经整理出交易之前策略构成的基本框架。下面我们对这一内容再次进行细化:
首先是`initialize()`函数的设置。这里我们沿用之前的做法,将其分为多个自定义函数的形式:
```
def initialize(context):
set_params() #设置相关参数
set_backtest() #设置回测基本数据
run_daily(trade, 'every_bar')
def set_params():
g.days=0 #起始日期为0
g.refresh_rate=10 #调仓周期设置为10日
def set_backtest():
set_benchmark('000905.XSHG') #将基准设置为中证500
set_option('use_real_price', True) #开启动态复权
log.set_level('order', 'error') #设定报错等级
```
以及对滑点、手续费、过滤停牌、退市及ST股票的设置
```
#每天开盘前要做的事情
def before_trading_start(context):
set_slip_fee(context)
# 根据不同的时间段设置滑点与手续费
def set_slip_fee(context):
set_slippage(FixedSlippage(0.02))
dt=context.current_dt
if dt>datetime.datetime(2013,1, 1):
set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5), type='stock')
else:
set_order_cost(OrderCost(open_tax=0, open_commission=0.003,close_commission=0.003, close_tax=0.001,min_commission=5), type='stock')
# 过滤停牌、退市和ST股票
def paused_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not current_data[stock].paused]
return security_list
def delisted_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not '退' in current_data[stock].name]
return security_list
def st_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not current_data[stock].is_st]
return security_list
```
###3 MACD数据读取###
根据我们策略的内容,我们采用的思路是:**筛选出股票池中质量较高的股票-->查询MACD指数-->买入卖出条件**
再次复习我们熟悉的选股方法:财务指标。这里我们挑选出符合:
10<市净率(pe_ratio)<40,每股盈余(eps)>0.3,净资产收益率(roe)>15,净利润环比增长率>0.3,并按照市净率进行升序排列,取前五十只。
```
stock_to_choose = get_fundamentals(query(
valuation.code, valuation.pe_ratio,
valuation.pb_ratio,valuation.market_cap,
indicator.eps, indicator.inc_net_profit_annual
).filter(
valuation.pe_ratio < 40,
valuation.pe_ratio > 10,
indicator.eps > 0.3,
indicator.inc_net_profit_annual > 0.30,
indicator.roe > 15
).order_by(
valuation.pb_ratio.asc()
).limit(
50), date=None)
```
将筛选后得到股票代码放置在stockpool的list列表中,以便之后对列表进行循环。
```
stockpool = list(stock_to_choose['code']) #将筛选出的股票代码存入list中
stockpool = paused_filter(stockpool)
stockpool = delisted_filter(stockpool)
stockpool = st_filter(stockpool) #过滤停牌、退市及ST股票
long_list = [] #买入列表
short_list = [] #卖出列表
hold = [] #持有列表
```
对于筛选出来的股票依次读取MACD数据。这里就需要用到`talib`库中`MACD`函数。TA-Lib中文全称为技术分析库,是一个广泛用在程序化交易中进行金融市场数据的技术分析的函数库。它提供了多种技术分析的函数,包含多种指标如**ADX, MACD, RSI, 布林轨道**等等,可以极大地方便我们量化投资中编程工作。
Talib中的MACD函数使用时格式为:
```
MACD(price, fastperiod=12, slowperiod=26, signalperiod=9)
```
传入函数的三个参数分别为短线周期、长线周期以及MACD的移动平滑周期。这里我们使用最常用的(12,26,9)组合。`MACD()`函数返回值共有三个,分别为**DIF,DEA和MACD**
```
if g.days%g.refresh_rate == 0:
for stock in stockpool:
prices = attribute_history(stock,300, '1d',['close']) #取较长周期的数据,这里为300日
price = array(prices['close']) #将price一列函数格式变为array
macd_tmp = talib.MACD(price, fastperiod=12, slowperiod=26, signalperiod=20) #将参数传入MACD函数中
DIF = macd_tmp[0] #返回的数据分别为短期慢线DIF、长期快线DEA及MACD
DEA = macd_tmp[1]
MACD = macd_tmp[2]
```
> 答疑与延伸:
> -***为什么要通过macd_tmp[0]来读取数据?*** 因为MACD返回的是一个包括三个元素的序列array,和从list中取元素类似,我们通过在其后加上[0][1]的方式分别取第一个元素和第二个元素。
> -***为什么要选取300日的历史数据?*** MACD存在一个问题,即若选取时间较短,则可能出现不收敛的状况,因此这里我们选取300日的数据。
###4 买入卖出条件设置###
策略中我们要判断长线和短线的位置问题。这里我们做一些简化,判断当日和三日前的MACD正负情况。
当日MACD为正且三日前MACD为负-->金叉形成 买入
当日MACD为负且三日前MACD为正-->死叉形成 卖出
将股票代码通过`append()`函数放在相应的买卖列表当中。
```
if MACD[-1] > 0 and MACD[-4] < 0:
long_list.append(stock) #将出现金叉的股票代码放入long_list中
elif MACD[-1] < 0 and MACD[-4] > 0:
short_list.append(stock) #将出现死叉的股票代码放入short_list中
stockset = list(context.portfolio.positions.keys()) #获得当前持仓状况
```
当对筛选后保留的股票进行循环之后,符合条件的股票已经被放入买入或卖出列表中。下面**查看当前所持有的股票**,如果在卖出列表当中则全部卖出,不是则保留,并将股票代码放入hold[]列表中
```
for stock in stockset:
if stock in short_list:
order_target_value(stock, 0) #卖出在short_list中的股票
else:
hold.append(stock) #保留不在short_list中的股票并将代码放在hold[]中,表示可以继续持有
```
买入long_list中除去已持有的股票:
```
buy_list = [] #设定一个新的空的list代表需要买入的股票
for stock in long_list:
if stock not in hold:
buy_list.append(stock) #把在long_list中目前未持有的股票放入buy_list中代表接下来需要买入的股票
```
将可用资金根据股票数量进行均分。为避免出现除数为0的状况在这里进行一个判断。
```
if len(buy_list)==0: #如果要买入列表股票数量为0
Cash = context.portfolio.available_cash
else:
Cash = context.portfolio.available_cash/len(buy_list)
for stock in buy_list:
order_target_value(stock, Cash)
g.days = 1
else:
g.days += 1 #天数+1进行调仓周期的循环
```
###策略完成,进行回测###
把买入卖出的代码写好,策略就写完了,完整代码如下:
```
import pandas as pd
import numpy as np
import talib ## 使用talib计算MACD的参数
def initialize(context):
set_params()
set_backtest()
run_daily(trade, 'every_bar')
def set_params():
g.days=0
g.refresh_rate=10
def set_backtest():
set_benchmark('000905.XSHG')
set_option('use_real_price', True)
log.set_level('order', 'error')
#每天开盘前要做的事情
def before_trading_start(context):
set_slip_fee(context)
# 根据不同的时间段设置滑点与手续费
def set_slip_fee(context):
set_slippage(FixedSlippage(0.02))
dt=context.current_dt
if dt>datetime.datetime(2013,1, 1):
set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5), type='stock')
else:
set_order_cost(OrderCost(open_tax=0, open_commission=0.003,close_commission=0.003, close_tax=0.001,min_commission=5), type='stock')
# 过滤停牌、退市、ST股票
def paused_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not current_data[stock].paused]
return security_list
def delisted_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not '退' in current_data[stock].name]
return security_list
def st_filter(security_list):
current_data = get_current_data()
security_list = [stock for stock in security_list if not current_data[stock].is_st]
return security_list
#######进行操作的过程#######
def trade(context):
stock_to_choose = get_fundamentals(query(
valuation.code, valuation.pe_ratio,
valuation.pb_ratio,valuation.market_cap,
indicator.eps, indicator.inc_net_profit_annual
).filter(
valuation.pe_ratio < 40,
valuation.pe_ratio > 10,
indicator.eps > 0.3,
indicator.inc_net_profit_annual > 0.30,
indicator.roe > 15
).order_by(
valuation.pb_ratio.asc()
).limit(
50), date=None)
stockpool = list(stock_to_choose['code'])
stockpool = paused_filter(stockpool)
stockpool = delisted_filter(stockpool)
stockpool = st_filter(stockpool)
long_list = []
short_list = []
hold = []
if g.days%g.refresh_rate == 0:
for stock in stockpool:
prices = attribute_history(stock,300, '1d',['close'])
price = array(prices['close'])
macd_tmp = talib.MACD(price, fastperiod=12, slowperiod=26, signalperiod=20)
DIF = macd_tmp[0]
DEA = macd_tmp[1]
MACD = macd_tmp[2]
# 判断MACD走向
if MACD[-1] > 0 and MACD[-4] < 0:
long_list.append(stock)
elif MACD[-1] < 0 and MACD[-4] > 0:
short_list.append(stock)
stockset = list(context.portfolio.positions.keys())
for stock in stockset:
if stock in short_list:
order_target_value(stock, 0)
else:
hold.append(stock)#如果不在卖出列表里则持有
buy_list = []
for stock in long_list:
if stock not in hold:
buy_list.append(stock)#新增的买入股票
if len(buy_list)==0:
Cash = context.portfolio.available_cash
else:
Cash = context.portfolio.available_cash/len(buy_list)
for stock in buy_list:
order_target_value(stock, Cash)
g.days = 1
else:
g.days += 1
```
在2015.1.1-2016.12.31这段时间内进行回测,回测结果如图:
![gaitubao_com_14996953869582.jpeg][5]
[5]: https://image.joinquant.com/6567848cf65b91c4b4b4ba8486e26216
###5 向导式初体验###
前段时期,致力于与时俱进的JoinQuant平台引入了高大上又因吹斯汀的功能--向导式。今天借着MACD的学习,我们一起来初步体验一下向导式的魅力吧!
**> 新建策略**
首先点击上方“我的策略”,进入之后点击橘黄色的“策略生成器”就可以新建一个向导式策略啦!
![gaitubao_com_14962038585409.jpeg][6]
[6]: https://image.joinquant.com/9203baa8e29db48a2bba8ed2ad65d83e
打开之后呈现出的界面如下图所示。和平时写策略的思路一样,向导式分成几个大的部分:股票池的选取及筛选、个股买入设置、个股卖出设置、风险控制和其他参数。下面我们一部分一部分进行向导式的构建。
![gaitubao_com_14958545614373.jpeg][7]
[7]: https://image.joinquant.com/1f260f41237228f7a52684e6b8950b52
**> 股票池的筛选**
在这一策略中,我们的选股条件是:沪深300成分股中,选择10<市盈率(pe_ratio)<40, 市净率(pb_ratio)<3, 净利润环比增长率(inc_net_profit_annual)>0.3,净资产收益率(roe)>15的股票,并按照销售毛利率进行降序排列,选取前50只。
在向导式中,每一指标旁边选项框的下拉箭头,分别根据需求设置股票池、ST选项以及是否过滤停牌和退市。
![gaitubao_com_14958552709681.jpeg][8]
[8]: https://image.joinquant.com/10436bf613a9bf102b23129665edea8c
策略中我们通过财务指标对股票进行了筛选。用动态市盈率的设定来举个栗子
![gaitubao_com_14958558428650.jpeg][9]
[9]: https://image.joinquant.com/e88b344efac8aad6f31423c5b95e1974
当鼠标点击“选股”按钮时,右侧就会出现选股指标。将鼠标放在**想选择的指标名称上**并单击左键,即可将该指标添加至左侧选股界面中,同时已经添加的指标会变成灰色表明不可重复添加。点击“动态市盈率”,这里我们需要将其限制在(30,40)区间中,在左侧界面“选项”下方下拉菜单选择“区间”并在后面将倍数相应改成10和40即可。
细心的朋友可能会注意到,在右侧“财务指标”栏旁边“附加条件”,点击之后可以看到这里我们可以添加股票池更新频率。在这个策略中我们更新频率是10个交易日,在左侧进行更改即可。
![gaitubao_com_14958563889699.jpeg][10]
[10]: https://image.joinquant.com/576c411dfd6d11844f694e953daae732
向导式还能够实现排序功能。在“选股”选项右侧有“排序”按钮,点击即可进入排序的设置。与选股指标类似,排序也是根据财务指标进行。同时还可以对升序/降序进行选择
![gaitubao_com_14958571343902.jpeg][11]
[11]: https://image.joinquant.com/8d14bc1ff6491c80cffe35001e11cb7f
向导式中添加指标时,点击对应项目下方的加号按钮即可添加项目,或者直接在右侧选项中单击指标即可将指标自动添加到左侧界面中。
**> 个股买入设置**
类似的,我们设置买入内容。将鼠标放置在指标名称右侧灰色的圆圈套住的i标志上即可看到这一指标的含义及计算方法。
在本策略中,我们用到的是技术指标MACD。单击MACD,在其中选择MACD项,可看到屏幕左侧相应指标栏中出现了MACD指标。
![gaitubao_com_14961955263116.jpeg][12]
[12]: https://image.joinquant.com/e792b9521324128829c7860ee17afce4
我们本次策略是短期快线DIF向上穿过黄色的长期慢线DEA时,即出现做多金叉信号则买入。在向导式中我们直接在MACD指标中选择“金叉”即可。Joinquant平台默认的时间设置是(12,26,9),如果想进行更改则点击MACD所在行右侧图标进行快慢线时间跨度的更改。
![gaitubao_com_14961974986753.jpeg][13]
[13]: https://image.joinquant.com/4406196c3064c06ad143fb18fb466c48
**> 个股卖出设置**
卖出设置类比买入设置。在本策略中,我们卖出的条件是短期快线DIF向下穿过黄色的长期慢线DEA,即出现死叉信号。因此这里将MACD后面的类型改为“死叉”即可。同时保留默认设置,即不卖出欲买入股票。
**> 风险控制**
通过上一篇教程的学习,相信大家对于止盈止损的设置已经有了一定掌握。回想之前所介绍的个股止损方法,是通过函数利用当前价格和买入价格计算亏损比例实现的。因此我们至少需要一个自定义方程。向导式轻易解决了编写代码繁琐的问题。在向导式“风险控制”部分,我们可以直接设置个股止盈止损,还能选择多种止损方法如根据持仓价值、策略最大盈亏以及大盘信号等进行止损。之后我们会进一步进行介绍。
![gaitubao_com_14961980981536.jpeg][14]
[14]: https://image.joinquant.com/77e581c0bc671d01a4d9cc7d3536b05f
**> 其他参数**
“其他参数”模块和我们之前所提到的`initialize()`函数中的内容非常相似,即可以放入是否开启动态复权、设置滑点佣金、设置最大建仓数量等。
![gaitubao_com_14961987639917.jpeg][15]
[15]: https://image.joinquant.com/a5a72a348bf27875d85f0e62a7a94f84
**> 策略检验**
和普通策略相同,向导式策略右上角可以设置回测时期、初始资金等,点击“更多”也可以进行基准和频率的设置。进行简单回测可以点击“快速回测”,想看到完整回测结果则点击:运行回测”。
以上就是我们从MACD择时策略出发对向导式进行的初步探究,之后我们也会对向导式陆续展开介绍。大家空闲时间可以对向导式进行进一步探索,以寻找更加高质量的策略!
##自测与自学##
1.是否掌握技术指标MACD的含义
2.修改MACD函数,使得短线周期、长线周期以及MACD平滑周期分别为10、30、10,并尝试其他周期看是否能将结果进一步优化
3.修改买入卖出条件设置,例如买入条件改为“在零轴之上形成金叉”,将卖出条件改为“在零轴下方”形成死叉”
4.自主了解和MACD相关的一些走势概念,如佛手向上、小鸭出水、空中缆车等
LZ您好,请问这个能当作纯指标类策略的模版么
从前我是用通达信来写公式选股的
2017-06-02
g.refresh_rate=10
if g.days%g.refresh_rate == 0:
请问怎么理解,是否10天执行一次策略?
2017-06-02
支持这样的教程,让新手在脑海能有个总体轮廓。
同问
if g.days%g.refresh_rate == 0:
xxxx
xxxx
else:
g.days += 1
这里的if else 条件是想干什么
2017-06-19
你好,持仓5只的限制写在哪里了?另外,是否应该先过滤掉st、停牌这些再筛选?
2017-07-18
向导式要是能选完后转换成代码就好了。这样可以在上面改东西。减少自己出错的可能性
2017-09-26
在JQ中使用技术分析指标不是导入 technical_analysis 库嘛?
语句:from jqlib.technical_analysis import *
2018-01-05
@爱测试的我 我也觉得应该先过滤不必要的股票 再筛选
2018-01-15
难道没人发现 其实 只有开仓的时候 这一笔交易吗?
2015-01-05 09:30 大连圣亚(600593.XSHG) 买 市价单 5800股
文章中:
基本思路:
> - 筛选出符合:
10<市盈率(pe_ratio)<40, 市净率(pb_ratio)<3, 净利润环比增长率(inc_net_profit_annual)>0.3,净资产收益率(roe)>15的股票,***按照销售毛利率进行降序排列***,选取前50只
结果代码是按 PB排序 整体 思路是好的 就是 希望大家不要被误导
2018-01-16
@豆哥 7月16的那根长直线说明当时肯定清仓过啊,说明至少有两次而不是一次交易
2018-01-31
我回测了下,为什么只有一次买入大连圣亚,一直没有卖出?
2018-02-20
@又见飞刀 找到原因了吗,为什么只有一次买入大连圣亚,一直没有卖出?
2018-05-02
是的,写法有些问题,下面的交易函数,每天首先检查持有的股票是否MACD形态变坏,及时卖掉,然后再看是否有资金,如果有资金继续买入
2018-07-23
每天重新获取数据,重新计算MACD这样的话,相当于计算的滑动窗口的MACD,MACD 不具有连续性了
2018-08-14
@望月新二 一般来讲300条数据产生的误差可以忽略不计
2018-08-14