MACD(移动平均收敛散度)是量化交易中经典的趋势与动量结合指标,其核心价值在于通过短期与长期价格趋势的差异,捕捉市场动量变化与潜在趋势转折。
MACD 的构成包括四个部分:快速 EMA(指数移动平均线,通常为 12 日)、慢速 EMA(通常为 26 日)、MACD 线(快速 EMA 与慢速 EMA 的差值)、信号线(MACD 线的 9 日 EMA,用于平滑波动)。在实际应用中,MACD 的主要用途包括:判断趋势方向(MACD 线在零轴上方为多头趋势,下方为空头趋势)、识别动量变化(MACD 线与价格的背离暗示趋势可能反转)、确认趋势确立(信号线穿越零轴标志趋势形成)。
本文解析的代码基于 quantstrat 包实现了一套简单的 MACD 趋势跟踪策略,以 AAPL 为标的,核心逻辑为:当信号线从下方穿越零轴(上穿)时,视为多头趋势确立,触发买入信号;当信号线从上方穿越零轴(下穿)时,视为趋势转弱,触发卖出信号,通过这种方式跟踪中长期趋势并捕获收益。
# Simple MACD strategy
#
# MACD可通过多种方式应用,本策略展示其作为趋势指标的用法。
#
# 传统上,当MACD信号线穿越零时,标志着正向趋势的确立。
#
# 本策略将在信号线"signal"上穿零轴时买入,下穿零轴时卖出。
#
# Author: brian
###############################################################################
# 加载quantstrat包,用于量化策略开发、回测与绩效分析
require(quantstrat)
# 清理旧策略数据,确保回测环境纯净(避免残留的订单、账户数据干扰结果)
suppressWarnings(rm("order_book.macd", pos=.strategy))
suppressWarnings(rm("account.macd", "portfolio.macd", pos=.blotter))
suppressWarnings(rm("account.st", "portfolio.st", "stock.str", "stratMACD", "startDate", "initEq", 'start_t', 'end_t'))
# 处理时区问题:若当前时区未设置,默认设为GMT,避免日期处理异常
oldtz <- Sys.getenv('TZ')
if (oldtz == '') {
Sys.setenv(TZ = "GMT")
}
# 设定交易标的为苹果公司股票(AAPL)
stock.str <- 'AAPL'
# MACD指标参数设置
fastMA = 12 # 快速EMA周期(传统参数12日)
slowMA = 26 # 慢速EMA周期(传统参数26日)
signalMA = 9 # 信号线周期(传统参数9日)
maType = "EMA" # 均线类型为指数移动平均(EMA)
# 定义货币单位与标的属性:AAPL以美元计价,合约乘数为1(股票交易通常乘数为1)
currency('USD')
## [1] "USD"
stock(stock.str, currency = 'USD', multiplier = 1)
## [1] "AAPL"
# 回测基础参数
startDate = '2006-12-31' # 数据起始日期
initEq = 1000000 # 初始资金100万美元
portfolio.st = 'macd' # 投资组合名称
account.st = 'macd' # 账户名称
# 初始化回测环境
initPortf(portfolio.st, symbols = stock.str) # 创建包含AAPL的投资组合
## [1] "macd"
initAcct(account.st, portfolios = portfolio.st) # 初始化账户,关联投资组合
## [1] "macd"
initOrders(portfolio = portfolio.st) # 初始化订单簿,记录交易订单
# 定义策略对象,名称与组合一致,并存入环境
strat.st <- portfolio.st
strategy(strat.st, store = TRUE)
# 添加MACD指标:基于收盘价计算,参数为快速EMA、慢速EMA
add.indicator(
strat.st,
name = "MACD", # 使用TTR包中的MACD函数
arguments = list(
x = quote(Cl(mktdata)), # 输入为收盘价(Cl(mktdata))
nFast = fastMA, # 快速EMA周期12日
nSlow = slowMA # 慢速EMA周期26日
),
label = '_' # 指标标签,用于后续信号引用
)
## [1] "macd"
# 生成交易信号:基于信号线与零轴的交叉
# 信号1:信号线上穿零轴(大于零且交叉),视为买入信号
add.signal(
strat.st,
name = "sigThreshold", # 阈值型信号函数
arguments = list(
column = "signal._", # 引用MACD指标中的信号线(因指标标签为'_',故列名为signal._)
relationship = "gt", # 大于(greater than)
threshold = 0, # 阈值为0(零轴)
cross = TRUE # 必须穿越阈值(避免持续触发)
),
label = "signal.gt.zero" # 信号标签:上穿零轴
)
## [1] "macd"
# 信号2:信号线下穿零轴(小于零且交叉),视为卖出信号
add.signal(
strat.st,
name = "sigThreshold",
arguments = list(
column = "signal._",
relationship = "lt", # 小于(less than)
threshold = 0,
cross = TRUE
),
label = "signal.lt.zero" # 信号标签:下穿零轴
)
## [1] "macd"
# 定义交易规则:将信号转化为具体交易动作
####
# 入场规则:当买入信号触发时,以市价单买入100股
add.rule(
strat.st,
name = 'ruleSignal', # 基于信号的规则函数
arguments = list(
sigcol = "signal.gt.zero", # 触发信号列:上穿零轴
sigval = TRUE, # 信号值为TRUE时执行
orderqty = 100, # 订单数量:100股
ordertype = 'market', # 市价单(确保及时入场)
orderside = 'long', # 做多方向
threshold = NULL # 无额外阈值
),
type = 'enter', # 入场规则
label = 'enter',
storefun = FALSE # 不存储规则函数结果
)
## [1] "macd"
# 可选的风险止损规则(注释未启用):
# 1. 止损限价单:当价格回落5%时触发止损
# add.rule(strat.st,name='ruleSignal', arguments = list(sigcol="signal.gt.zero",sigval=TRUE, orderqty='all', ordertype='stoplimit', orderside='long', threshold=-.05,tmult=TRUE, orderset='exit2'),type='chain', parent='enter', label='risk',storefun=FALSE)
# 2. 追踪止损单:当价格从高点回落1单位时触发止损
# add.rule(strat.st,name='ruleSignal', arguments = list(sigcol="signal.gt.zero",sigval=TRUE, orderqty='all', ordertype='stoptrailing', orderside='long', threshold=-1,tmult=FALSE, orderset='exit2'), type='chain', parent='enter', label='trailingexit')
# 出场规则:当卖出信号触发时,以市价单平仓所有持仓
add.rule(
strat.st,
name = 'ruleSignal',
arguments = list(
sigcol = "signal.lt.zero", # 触发信号列:下穿零轴
sigval = TRUE,
orderqty = 'all', # 平仓全部持仓
ordertype = 'market', # 市价单(确保及时离场)
orderside = 'long', # 针对多头持仓
threshold = NULL,
orderset = 'exit2' # 订单集标签,用于管理出场订单
),
type = 'exit', # 出场规则
label = 'exit'
)
## [1] "macd"
# 结束规则定义
####
# 获取历史数据:从雅虎财经下载AAPL的历史数据(2006-12-31至2014-06-01)
getSymbols(stock.str, from = startDate, to = '2014-06-01', src = 'yahoo')
## [1] "AAPL"
# 执行策略回测并记录时间
start_t <- Sys.time() # 回测开始时间
out <- applyStrategy(
strat.st,
portfolios = portfolio.st,
parameters = list(nFast = fastMA, nSlow = slowMA, nSig = signalMA, maType = maType), # 传递MACD参数
verbose = TRUE # 输出详细过程
)
## [1] "2007-03-16 00:00:00 AAPL 100 @ 3.19964289665222"
## [1] "2007-08-17 00:00:00 AAPL -100 @ 4.35928583145142"
## [1] "2007-09-05 00:00:00 AAPL 100 @ 4.88428592681885"
## [1] "2008-01-16 00:00:00 AAPL -100 @ 5.70142889022827"
## [1] "2008-03-31 00:00:00 AAPL 100 @ 5.125"
## [1] "2008-06-26 00:00:00 AAPL -100 @ 6.00928592681885"
## [1] "2008-08-20 00:00:00 AAPL 100 @ 6.28000020980835"
## [1] "2008-09-09 00:00:00 AAPL -100 @ 5.41714286804199"
## [1] "2009-02-06 00:00:00 AAPL 100 @ 3.56142902374268"
## [1] "2009-03-04 00:00:00 AAPL -100 @ 3.25607109069824"
## [1] "2009-03-19 00:00:00 AAPL 100 @ 3.62928605079651"
## [1] "2009-12-15 00:00:00 AAPL -100 @ 6.93464279174805"
## [1] "2009-12-29 00:00:00 AAPL 100 @ 7.46785688400269"
## [1] "2010-02-03 00:00:00 AAPL -100 @ 7.11535692214966"
## [1] "2010-03-04 00:00:00 AAPL 100 @ 7.52535676956177"
## [1] "2010-07-16 00:00:00 AAPL -100 @ 8.92500019073486"
## [1] "2010-08-03 00:00:00 AAPL 100 @ 9.35464286804199"
## [1] "2010-08-17 00:00:00 AAPL -100 @ 8.99892902374268"
## [1] "2010-09-15 00:00:00 AAPL 100 @ 9.65071392059326"
## [1] "2011-03-22 00:00:00 AAPL -100 @ 12.1857137680054"
## [1] "2011-05-03 00:00:00 AAPL 100 @ 12.4357137680054"
## [1] "2011-05-23 00:00:00 AAPL -100 @ 11.9428567886353"
## [1] "2011-07-11 00:00:00 AAPL 100 @ 12.6428565979004"
## [1] "2011-11-16 00:00:00 AAPL -100 @ 13.7417860031128"
## [1] "2011-12-27 00:00:00 AAPL 100 @ 14.518928527832"
## [1] "2012-05-09 00:00:00 AAPL -100 @ 20.3278560638428"
## [1] "2012-06-20 00:00:00 AAPL 100 @ 20.9192867279053"
## [1] "2012-10-12 00:00:00 AAPL -100 @ 22.4896430969238"
## [1] "2013-05-09 00:00:00 AAPL 100 @ 16.3132133483887"
## [1] "2013-06-19 00:00:00 AAPL -100 @ 15.1071434020996"
## [1] "2013-07-26 00:00:00 AAPL 100 @ 15.7496433258057"
## [1] "2014-01-22 00:00:00 AAPL -100 @ 19.6967868804932"
## [1] "2014-01-24 00:00:00 AAPL 100 @ 19.5025005340576"
## [1] "2014-01-29 00:00:00 AAPL -100 @ 17.8839282989502"
## [1] "2014-03-26 00:00:00 AAPL 100 @ 19.2778568267822"
## [1] "2014-04-15 00:00:00 AAPL -100 @ 18.498571395874"
## [1] "2014-04-29 00:00:00 AAPL 100 @ 21.1546421051025"
end_t <- Sys.time() # 回测结束时间
print(end_t - start_t) # 打印回测耗时
## Time difference of 0.4202731 secs
# 更新组合与账户数据
start_t <- Sys.time()
updatePortf(Portfolio = portfolio.st, Dates = paste('::', as.Date(Sys.time()), sep = '')) # 更新至当前日期
## [1] "macd"
end_t <- Sys.time()
print("trade blotter portfolio update:")
## [1] "trade blotter portfolio update:"
print(end_t - start_t) # 打印数据更新耗时
## Time difference of 0.032722 secs
# 可视化回测结果
chart.Posn(Portfolio = portfolio.st, Symbol = stock.str) # 绘制AAPL的持仓变化图
plot(add_MACD(fast = fastMA, slow = slowMA, signal = signalMA, maType = "EMA")) # 叠加MACD指标
# 查看订单簿(包含所有交易订单详情,用于调试与分析)
obook <- getOrderBook('macd')
# 恢复初始时区设置
Sys.setenv(TZ = oldtz)
策略核心思路
该策略以 “趋势跟踪” 为核心,通过 MACD 信号线与零轴的交叉信号构建完整的交易逻辑。
代码首先清理旧有策略数据,避免残留信息干扰回测;通过时区设置确保日期处理准确;定义交易标的(AAPL)、初始资金(100 万美元)及 MACD 核心参数(12/26/9 日 EMA),为策略运行奠定基础。这一步的关键是确保回测环境的纯净性与参数的明确性,避免后续结果出现偏差。
策略通过add.indicator添加 MACD 指标,基于收盘价计算快速 EMA、慢速 EMA 及信号线;再通过add.signal将指标转化为可执行的交易信号 —— 当信号线从下方穿越零轴时,认为多头趋势确立(买入信号);当信号线从上方穿越零轴时,认为趋势转弱(卖出信号)。这种 “指标→信号” 的转化,是将技术分析逻辑量化的核心步骤。
信号需要通过具体的交易规则转化为订单。策略中,add.rule分别定义了入场与出场规则:入场时以市价单买入 100 股(跟随买入信号),出场时以市价单平仓所有持仓(跟随卖出信号)。代码中还预留了止损规则(未启用),为风险控制提供了扩展空间,体现了 “趋势收益捕获” 与 “风险控制” 的平衡思路。
策略通过applyStrategy执行回测,记录耗时以评估效率;更新组合与账户数据确保交易记录与资金权益同步;最后通过chart.Posn与add_MACD可视化持仓变化与指标走势,直观展示交易时机与信号的对应关系,为策略效果评估提供直观依据。
MACD 及策略的优劣势与改进方向
MACD 的优势在于兼顾趋势与动量,通过 EMA 平滑降低噪音,且零轴与交叉信号直观易懂,适合趋势跟踪场景。但其劣势也较为明显:作为滞后指标(基于历史价格计算),信号可能滞后于实际趋势转折,导致入场 / 出场时机偏晚;在横盘震荡市中,信号线可能频繁穿越零轴,产生大量虚假信号,降低策略效率。
策略的优势体现在逻辑简洁与可操作性强:基于明确的信号规则,避免主观判断,便于复现与参数优化;市价单入场出场确保了信号触发时的执行力。但策略也存在局限:未启用止损规则,若趋势突然反转可能导致大额亏损;单一依赖 MACD 信号,缺乏对市场波动率的考量,在高波动环境下风险较高;固定仓位(100 股)未考虑资金管理,可能无法适配不同资金规模。
未来可从三方面优化策略:一是添加动态止损机制(如追踪止损),限制单笔交易亏损;二是结合波动率指标(如 ATR)过滤信号,在震荡市减少交易频率;三是引入参数优化(如通过前进测试调整 EMA 周期),使策略适配不同市场阶段。通过这些改进,可提升策略的稳健性与实盘适用性,更好地平衡收益与风险。