问题描述
解决方案
这是一个基于 TRIX(三重指数平滑移动平均) 指标的量化交易策略。
策略逻辑说明
TRIX 是一种动量指标,用于过滤短期价格波动,通过三次平滑处理来显示价格的长期趋势。
- 指标计算:
- TR (EMA3):对收盘价进行三次 N 日指数移动平均(EMA)。
- TRIX:(今日 TR - 昨日 TR) / 昨日 TR * 100。
- MATRIX (TRIXMA):TRIX 的 M 日简单移动平均。
- 交易信号:
- 金叉(买入):TRIX 线由下向上穿过 MATRIX 线。
- 死叉(卖出):TRIX 线由上向下穿过 MATRIX 线。
- 参数设置:
- N = 12 (TRIX 周期)
- M = 9 (MATRIX 周期)
策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置策略参数
ContextInfo.N = 12 # TRIX指标的周期
ContextInfo.M = 9 # TRIX均线(MATRIX)的周期
# 2. 设置股票池 (示例:平安银行)
# 在实际运行时,可以通过界面设置股票池,这里代码演示写死一个
ContextInfo.stock_list = ['000001.SZ']
ContextInfo.set_universe(ContextInfo.stock_list)
# 3. 设置资金账号 (请替换为您自己的资金账号)
ContextInfo.account_id = '6000000000'
ContextInfo.account_type = 'STOCK' # 账号类型:STOCK股票,FUTURE期货
ContextInfo.set_account(ContextInfo.account_id)
# 4. 设置回测参数 (仅回测有效)
ContextInfo.set_slippage(1, 0.0) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0013, 0.0003, 0.0003, 0.0003, 5]) # 设置费率
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前设置的股票池
stock_list = ContextInfo.get_universe()
# 获取当前周期 (例如 '1d')
period = ContextInfo.period
# 遍历每只股票进行计算
for stock in stock_list:
# 1. 获取历史行情数据
# TRIX经过三次平滑,需要较长的历史数据才能计算准确,这里取 100 根K线
data_len = 100
# 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
# subscribe=True 确保实盘时数据自动更新
market_data = ContextInfo.get_market_data_ex(
['close'],
[stock],
period=period,
count=data_len,
dividend_type='front', # 前复权
subscribe=True
)
if stock not in market_data or market_data[stock].empty:
continue
df = market_data[stock]
# 数据长度不足以计算指标时跳过
if len(df) < ContextInfo.N * 3 + ContextInfo.M:
continue
# 2. 计算 TRIX 指标
# 计算步骤 1: 收盘价的 N 日 EMA (EMA1)
ema1 = df['close'].ewm(span=ContextInfo.N, adjust=False).mean()
# 计算步骤 2: EMA1 的 N 日 EMA (EMA2)
ema2 = ema1.ewm(span=ContextInfo.N, adjust=False).mean()
# 计算步骤 3: EMA2 的 N 日 EMA (EMA3, 即 TR)
tr = ema2.ewm(span=ContextInfo.N, adjust=False).mean()
# 计算步骤 4: TRIX = (今日TR - 昨日TR) / 昨日TR * 100
# pct_change() 计算变化率,默认就是 (curr - prev) / prev
trix = tr.pct_change() * 100
# 计算步骤 5: MATRIX (TRIX 的 M 日简单移动平均)
matrix = trix.rolling(window=ContextInfo.M).mean()
# 3. 获取最新的指标值用于判断信号
# prev_trix: 上一根K线的TRIX, curr_trix: 当前K线的TRIX
# iloc[-1] 是当前最新值,iloc[-2] 是前一根K线值
curr_trix = trix.iloc[-1]
prev_trix = trix.iloc[-2]
curr_matrix = matrix.iloc[-1]
prev_matrix = matrix.iloc[-2]
# 检查是否包含无效值(NaN),如果有则跳过
if np.isnan(curr_trix) or np.isnan(curr_matrix) or np.isnan(prev_trix) or np.isnan(prev_matrix):
continue
# 4. 获取当前持仓信息
# 获取持仓对象列表
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
current_vol = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock:
current_vol = pos.m_nVolume
break
# 5. 生成交易信号并下单
# 金叉:TRIX 上穿 MATRIX
long_signal = (prev_trix < prev_matrix) and (curr_trix > curr_matrix)
# 死叉:TRIX 下穿 MATRIX
short_signal = (prev_trix > prev_matrix) and (curr_trix < curr_matrix)
# 执行买入逻辑
if long_signal:
if current_vol == 0: # 如果当前无持仓
# 示例:全仓买入 (实际策略请做好资金管理)
# 这里简单演示:按最新价买入 1000 股
# 23: 买入, 1101: 单股单账号普通股/手下单, 5: 最新价
passorder(23, 1101, ContextInfo.account_id, stock, 5, -1, 1000, ContextInfo)
print(f"TRIX金叉,买入 {stock}")
# 执行卖出逻辑
elif short_signal:
if current_vol > 0: # 如果当前有持仓
# 卖出所有持仓
# 24: 卖出
passorder(24, 1101, ContextInfo.account_id, stock, 5, -1, current_vol, ContextInfo)
print(f"TRIX死叉,卖出 {stock}")
# 6. 绘图 (可选,用于回测界面展示)
# 注意:paint函数只能画在主图对应的品种上,如果遍历多个股票,这里只演示画当前主图品种
# 获取当前主图代码
main_stock = ContextInfo.stockcode + '.' + ContextInfo.market
if main_stock in market_data:
# 重新计算一遍主图的指标用于绘图
m_df = market_data[main_stock]
m_ema1 = m_df['close'].ewm(span=ContextInfo.N, adjust=False).mean()
m_ema2 = m_ema1.ewm(span=ContextInfo.N, adjust=False).mean()
m_tr = m_ema2.ewm(span=ContextInfo.N, adjust=False).mean()
m_trix = m_tr.pct_change() * 100
m_matrix = m_trix.rolling(window=ContextInfo.M).mean()
# 在副图画线
ContextInfo.paint('TRIX', m_trix.iloc[-1], -1, 0, 'white', 'noaxis')
ContextInfo.paint('MATRIX', m_matrix.iloc[-1], -1, 0, 'yellow', 'noaxis')
代码关键点解析
-
数据获取 (
get_market_data_ex):- 使用了
get_market_data_ex接口,这是 QMT 推荐的高效数据接口。 count=100:TRIX 指标涉及三次 EMA 平滑,对历史数据长度有要求,数据太短会导致计算初期的指标值不准确。dividend_type='front':使用前复权数据,保证价格连续性,避免分红除权造成的指标跳变。
- 使用了
-
指标计算 (Pandas):
- 利用 Pandas 的
ewm(span=N)函数非常方便地计算指数移动平均。 pct_change()用于计算变化率,即(当前值 - 前值) / 前值,这正是 TRIX 的核心定义。
- 利用 Pandas 的
-
交易执行 (
passorder):23代表买入,24代表卖出。1101代表单股、单账号、按股数下单。5代表使用最新价下单。- 代码中加入了简单的持仓判断:金叉时若无持仓则买入,死叉时若有持仓则卖出。
-
绘图 (
paint):- 代码末尾包含了
paint函数,可以在 QMT 的回测界面副图中画出 TRIX 和 MATRIX 线,方便验证策略信号。
- 代码末尾包含了
如何使用
- 打开 QMT 客户端,进入“策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码复制粘贴到编辑器中。
- 修改
init函数中的ContextInfo.account_id为您真实的资金账号。 - 点击“编译”检查是否有语法错误。
- 在回测模式下选择标的和时间段进行回测,或在实盘模式下运行。
Q&A
Q: 为什么 TRIX 指标计算需要较长的历史数据?
A: TRIX 进行了三次指数平滑(EMA)。EMA 是一种递归计算,今天的数值依赖于昨天的数值。如果数据序列太短,初始值的偏差会严重影响后续的计算结果。通常建议数据长度至少是周期 N 的 3-5 倍以上。
Q: 如何调整策略的灵敏度?
A: 修改 init 函数中的 ContextInfo.N 和 ContextInfo.M。
- 减小 N(例如改为 5):指标对价格变化更敏感,信号更多,但假信号也更多。
- 增大 N(例如改为 30):指标更平滑,过滤震荡效果更好,但信号会有明显的滞后。
Q: passorder 函数中的 quickTrade 参数有什么用?
A: 在本策略中,passorder 的 quickTrade 参数(倒数第二个参数)默认为 0(代码中未显式写出,使用了默认行为)。在回测中,这意味着信号在 K 线走完后确认,并在下一根 K 线开盘时下单。如果设置为 1,则表示立即下单(非收盘确认),这在实盘中常用于盘中即时信号,但在回测中可能会引入“未来函数”风险,需谨慎使用。