python实现MACD均线择时策略

  • Post author:
  • Post category:python


本文采用了聚宽平台接口进行量化策略设置:



1 确定策略内容

在之前的教程中,我们学习了如何通过财务指标等对股票进行筛选等操作。今天我们将以MACD为例,探究如何利用

技术指标

进行策略的构建与实现。


> MACD的组成


MACD(Moving Average Convergence and Divergence)即

指数平滑移动平均线

,由Geral Appel 于1970年提出,属于

大势趋势类指标

,它由***长期慢速均线DEA,短期快线的DIF,红色能量柱(多头),绿色能量柱(空头)、0轴(多空分界线)***五部分组成,即“两线两柱一轴”组合起来形成。

gaitubao_com_14957648667277.jpeg

在图中表示出来则为:

gaitubao_com_14957632961470.jpg

图中水平线为零轴,又称多空分水岭,顾名思义上方为多头势力较强,下方则为空头势力较强。

当短期快线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


> DIF与DEA死叉的使用


当白线即短期快线DIF向下穿过黄色的长期慢线DEA时,即“

快线低于慢线

”,属于做空死叉信号。死叉同样需要结合零轴。

当零轴之上出现死叉时,一般认为是股价上升趋势途中的短暂回调,后市股价仍有再次走强的可能。

当零轴之下发生死叉时,一般认为是股价下跌趋势途中的继续回调,后市股价惯性下跌的概率较高,尤其是MACD零轴之下出现二次死叉时,股价更容易出现加速下跌行情,所以零轴之下,无论出现的是一次死叉或二次死叉,投资者都应当空仓观望,静观其变。


策略内容


了解了MACD的基本情况,我们就能够制定本次利用技术指标构建的策略内容。

基本思路:

  • 筛选出符合:

    10<市盈率(pe_ratio)<40, 市净率(pb_ratio)❤️, 净利润环比增长率(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

的方式分别取第一个元素和第二个元素。




为什么要选取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



版权声明:本文为u010820857原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。