问题描述
解决方案
这是一个基于 JoinQuant(聚宽)平台的 MACD 指标金叉买入、死叉卖出的单股票策略。
策略逻辑说明
- 指标定义:使用标准的 MACD 参数(快线12,慢线26,信号线9)。
- 金叉(买入信号):当 DIF 线(快线)上穿 DEA 线(慢线/信号线)时,视为金叉,全仓买入。
- 死叉(卖出信号):当 DIF 线(快线)下穿 DEA 线(慢线/信号线)时,视为死叉,清仓卖出。
- 数据获取:每日获取过去 100 天的收盘价数据以计算指标。
- 计算库:使用
talib库进行高效的指标计算。
策略代码
# -*- coding: utf-8 -*-
# 导入函数库
from jqdata import *
import talib
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、股票池、费用等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 设定要操作的股票,这里以平安银行为例
g.security = '000001.XSHE'
# 设定MACD参数
g.fast_period = 12
g.slow_period = 26
g.signal_period = 9
# 设定交易费用(股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 每日开盘时运行
run_daily(market_open, time='every_bar')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 获取过去100天的收盘价数据(保证有足够的数据计算MACD)
# 注意:MACD计算需要一定长度的历史数据才能趋于稳定
close_data = attribute_history(security, 100, '1d', ['close'])
# 将数据转换为numpy数组,供talib使用
close_array = close_data['close'].values
# 使用talib计算MACD
# macd_dif: 差离值 (快线)
# macd_dea: 讯号线 (慢线)
# macd_hist: 红绿柱
macd_dif, macd_dea, macd_hist = talib.MACD(
close_array,
fastperiod=g.fast_period,
slowperiod=g.slow_period,
signalperiod=g.signal_period
)
# 获取当前的DIF和DEA值(数组最后一个元素)
current_dif = macd_dif[-1]
current_dea = macd_dea[-1]
# 获取上一时间点的DIF和DEA值(数组倒数第二个元素)
prev_dif = macd_dif[-2]
prev_dea = macd_dea[-2]
# 检查是否有持仓
current_position = context.portfolio.positions[security].closeable_amount
# 获取当前可用现金
cash = context.portfolio.available_cash
# 交易逻辑判断
# 金叉:上一时刻 DIF < DEA,且当前时刻 DIF > DEA
if prev_dif < prev_dea and current_dif > current_dea:
# 如果有现金,则全仓买入
if cash > 0:
order_value(security, cash)
log.info("MACD金叉,买入 %s" % security)
# 死叉:上一时刻 DIF > DEA,且当前时刻 DIF < DEA
elif prev_dif > prev_dea and current_dif < current_dea:
# 如果有持仓,则清仓卖出
if current_position > 0:
order_target(security, 0)
log.info("MACD死叉,卖出 %s" % security)
代码关键点解析
talib.MACD: 这是一个非常高效的技术分析库函数。它返回三个数组,分别是 DIF(快线)、DEA(慢线)和 MACD柱状图。attribute_history: 我们获取了过去 100 天的数据。虽然 MACD 参数是 12 和 26,但为了让 EMA(指数移动平均)的计算结果更加准确和收敛,通常需要比最长周期多 2-3 倍的数据量。- 交叉判断逻辑:
- 我们通过比较
[-1](当前值) 和[-2](前一日值) 来判断交叉。 - 如果昨天快线在慢线之下,今天快线在慢线之上,即为金叉。
- 如果昨天快线在慢线之上,今天快线在慢线之下,即为死叉。
- 我们通过比较
order_value与order_target:order_value(security, cash): 用当前所有现金买入股票。order_target(security, 0): 将股票仓位调整为 0,即清仓。
Q&A
Q: 为什么获取历史数据时要取 100 天,而不是 26 天?
A: MACD 的计算依赖于 EMA(指数移动平均),EMA 具有递归性质,初始值的设定会影响后续的计算结果。如果只取 26 天数据,计算出的 MACD 值在初期会非常不稳定且不准确。获取更多的数据(如 100 天或更多)可以让 EMA 充分收敛,使计算出的指标值与看盘软件上的数值一致。
Q: 如何将此策略修改为多只股票轮动?
A: 可以将 g.security 修改为一个股票列表(例如 g.stocks = ['000001.XSHE', '600519.XSHG']),然后在 market_open 函数中使用 for security in g.stocks: 循环遍历每一只股票,分别执行获取数据、计算指标和下单的逻辑。同时需要注意资金分配问题(例如每只股票分配总资金的 1/N)。
Q: 策略中的 set_option('use_real_price', True) 是什么作用?
A: 这行代码开启了动态复权模式(真实价格)。在回测中,股票会发生分红、拆股等事件,导致价格跳空。开启此选项后,聚宽会在回测引擎内部自动处理复权,使我们获取到的价格是连续的,避免因除权除息导致的技术指标计算错误(例如股价突然“腰斩”导致误触发死叉)。