问题描述
解决方案
这是一个基于 TRIX(三重指数平滑移动平均) 指标的量化交易策略。
策略原理
TRIX 指标是一种研究股价趋势的长期技术分析工具。它过滤掉了短期波动的干扰,主要用于判断中长期的趋势。
-
计算方法:
- 先计算收盘价的 N 日指数移动平均(EMA1);
- 对 EMA1 再计算 N 日指数移动平均(EMA2);
- 对 EMA2 再计算 N 日指数移动平均(EMA3);
- TRIX 线 = (EMA3 - 昨日EMA3) / 昨日EMA3 * 100;
- MATRIX 线 = TRIX 的 M 日简单移动平均。
-
交易逻辑:
- 金叉买入:当 TRIX 线由下向上突破 MATRIX 线时,视为买入信号。
- 死叉卖出:当 TRIX 线由上向下跌破 MATRIX 线时,视为卖出信号。
策略代码
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
def initialize(context):
"""
初始化函数,设定基准、手续费、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定股票类每笔交易时的手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# --- 策略参数设置 ---
# 要操作的标的(这里以平安银行为例,可修改为其他股票或ETF)
g.security = '000001.XSHE'
# TRIX指标参数
g.N = 12 # EMA的周期
g.M = 9 # TRIX均线(MATRIX)的周期
# 运行频率:每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日开盘运行的逻辑
"""
security = g.security
# 1. 获取历史数据
# TRIX需要三重平滑,需要较长的历史数据才能使计算稳定
# 获取长度建议为 N * 5 + M 以上
fetch_len = g.N * 10 + g.M
# 获取收盘价数据
# 注意:include_now=True 在回测中如果是按天运行,取不到当天收盘价,只能取到昨天及之前的
# 这里我们取过去 fetch_len 天的数据(不含今天)
h_data = attribute_history(security, fetch_len, '1d', ['close'], df=True)
if len(h_data) < fetch_len:
log.info("数据不足,跳过当日")
return
close_prices = h_data['close']
# 2. 计算 TRIX 指标
# 计算三重指数移动平均 (EMA3)
# span=g.N 对应 alpha=2/(N+1)
ema1 = close_prices.ewm(span=g.N, adjust=False).mean()
ema2 = ema1.ewm(span=g.N, adjust=False).mean()
ema3 = ema2.ewm(span=g.N, adjust=False).mean()
# 计算 TRIX: (EMA3 - Ref(EMA3, 1)) / Ref(EMA3, 1) * 100
# pct_change() 计算的是 (curr - prev) / prev,正好符合公式,再乘以100
trix = ema3.pct_change() * 100
# 计算 MATRIX (TRIX的M日简单移动平均)
matrix = trix.rolling(window=g.M).mean()
# 3. 获取信号判断所需的数值
# 当前时刻(昨天收盘)的指标值
curr_trix = trix.iloc[-1]
curr_matrix = matrix.iloc[-1]
# 前一时刻(前天收盘)的指标值
prev_trix = trix.iloc[-2]
prev_matrix = matrix.iloc[-2]
# 检查是否有 NaN 值(数据计算初期可能产生NaN)
if np.isnan(curr_trix) or np.isnan(curr_matrix) or np.isnan(prev_trix) or np.isnan(prev_matrix):
return
# 4. 交易逻辑判断
# 获取当前仓位
position = context.portfolio.positions[security]
cash = context.portfolio.available_cash
# 金叉:TRIX 上穿 MATRIX
is_golden_cross = (prev_trix < prev_matrix) and (curr_trix > curr_matrix)
# 死叉:TRIX 下穿 MATRIX
is_death_cross = (prev_trix > prev_matrix) and (curr_trix < curr_matrix)
# 执行交易
if is_golden_cross:
# 如果有资金且未持仓(或根据策略加仓),则买入
if cash > 0:
log.info("TRIX金叉 (TRIX:%.3f, MATRIX:%.3f),买入 %s" % (curr_trix, curr_matrix, security))
order_value(security, cash)
elif is_death_cross:
# 如果持有仓位,则卖出
if position.closeable_amount > 0:
log.info("TRIX死叉 (TRIX:%.3f, MATRIX:%.3f),卖出 %s" % (curr_trix, curr_matrix, security))
order_target(security, 0)
# 记录指标值以便在回测图中查看
record(TRIX=curr_trix, MATRIX=curr_matrix)
代码要点解析
-
数据获取 (
attribute_history):- TRIX 指标涉及三次 EMA 平滑,对历史数据的依赖性较强。如果获取的数据长度太短,计算出的 EMA 值在初期会很不准确。因此代码中设置了
fetch_len = g.N * 10 + g.M,获取足够多的历史数据以确保指标收敛。
- TRIX 指标涉及三次 EMA 平滑,对历史数据的依赖性较强。如果获取的数据长度太短,计算出的 EMA 值在初期会很不准确。因此代码中设置了
-
指标计算 (
pandas.ewm):- 使用了 Pandas 的
ewm(span=N, adjust=False)来计算指数移动平均。adjust=False是为了更贴近传统股票软件(如通达信、同花顺)的递归计算方式。 trix = ema3.pct_change() * 100:利用pct_change快速计算变化率。
- 使用了 Pandas 的
-
信号判断:
- 通过比较“当前值”和“前一值”与信号线的相对位置来判断交叉。
prev_trix < prev_matrix且curr_trix > curr_matrix判定为金叉。
-
交易执行:
- 使用
order_value全仓买入(可根据需求调整仓位管理逻辑)。 - 使用
order_target清仓卖出。
- 使用
如何使用
- 在聚宽(JoinQuant)的“我的策略”中新建一个策略。
- 将上述代码复制粘贴到代码编辑区。
- 点击“编译运行”检查是否有语法错误。
- 点击“运行回测”,选择回测时间段(建议选择较长的时间段,如1-2年,因为 TRIX 是中长线指标)。
Q&A
Q: 如何更换要操作的股票?
A: 修改 initialize 函数中的 g.security = '000001.XSHE',将其改为你想要回测的股票代码(例如贵州茅台 '600519.XSHG')。
Q: 如何调整 TRIX 的灵敏度?
A: 修改 g.N 和 g.M。
- 增大
g.N(如改为 20或30),指标会更平滑,过滤更多噪音,但信号会更滞后(适合长线)。 - 减小
g.N(如改为 5或8),指标会更敏感,信号更多,但假信号也可能增加(适合短线)。
Q: 为什么回测初期没有交易?
A: 因为 TRIX 需要较长的历史数据来计算 EMA3。虽然我们获取了历史数据,但如果回测开始日期对应的历史数据不足以让 EMA 收敛,或者指标在初期没有发生交叉,就不会产生交易。