问题描述
解决方案
这是一个基于 MFI (Money Flow Index, 资金流量指标) 的 QMT 量化交易策略。
策略逻辑说明
MFI 指标结合了价格和成交量,用于衡量市场的买卖压力。它通常在 0 到 100 之间波动。
- 买入信号(超卖):当 MFI 值低于 20 时,认为市场处于超卖状态,资金可能开始回流,产生买入信号。
- 卖出信号(超买):当 MFI 值高于 80 时,认为市场处于超买状态,资金可能流出,产生卖出信号。
- 持仓管理:
- 触发买入信号且当前无持仓时,全仓买入。
- 触发卖出信号且当前有持仓时,清仓卖出。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 1. 设置策略参数
ContextInfo.mfi_period = 14 # MFI 计算周期
ContextInfo.buy_threshold = 20 # 买入阈值 (超卖)
ContextInfo.sell_threshold = 80 # 卖出阈值 (超买)
# 2. 设置股票池 (示例:浦发银行,平安银行)
# 在实际运行时,可以通过界面设置股票池,也可以在这里代码指定
ContextInfo.stock_list = ['600000.SH', '000001.SZ']
ContextInfo.set_universe(ContextInfo.stock_list)
# 3. 设置资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 4. 设置回测参数 (仅回测有效)
ContextInfo.set_slippage(1, 0.002) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0013, 0.0003, 0.0003, 0.0003, 5]) # 设置手续费
def handlebar(ContextInfo):
"""
行情事件函数,每根K线或每个Tick调用一次
"""
# 获取当前设置的股票池
stock_list = ContextInfo.get_universe()
# 获取当前周期 (例如 '1d')
period = ContextInfo.period
# 确保是最后一根K线才下单 (避免历史K线重复下单,实盘中通常需要此判断)
# 如果是回测模式,平台会自动处理,但加上此判断逻辑更严谨
if not ContextInfo.is_last_bar():
# 回测模式下,如果不是最后一根bar,通常由平台驱动循环,这里直接return可能导致回测不执行
# 注意:QMT回测机制中,handlebar会在每一根历史K线上执行。
# 实盘中,通常只在最新的K线(is_last_bar)处理交易。
# 为了兼容回测和实盘,这里不做 return,而是依赖 QMT 的机制。
pass
# 获取历史行情数据
# 我们需要 High, Low, Close, Volume 来计算 MFI
# 多取一些数据以保证指标计算的稳定性,例如取 period * 3
count = ContextInfo.mfi_period * 3
# 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
# 返回格式: {code: DataFrame}
market_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close', 'volume'],
stock_list,
period=period,
count=count,
dividend_type='front' # 前复权
)
for stock in stock_list:
# 获取该股票的 DataFrame 数据
if stock not in market_data:
continue
df = market_data[stock]
# 数据长度不足,无法计算指标,跳过
if len(df) < ContextInfo.mfi_period + 1:
continue
# 提取 numpy 数组供 talib 使用
# 注意:talib 需要 float 类型
high_prices = df['high'].values.astype(float)
low_prices = df['low'].values.astype(float)
close_prices = df['close'].values.astype(float)
volumes = df['volume'].values.astype(float)
# --- 计算 MFI 指标 ---
try:
mfi_values = talib.MFI(high_prices, low_prices, close_prices, volumes, timeperiod=ContextInfo.mfi_period)
except Exception as e:
print(f"计算 {stock} MFI 出错: {e}")
continue
# 获取最新的 MFI 值 (数组的最后一个)
# 注意:如果数据包含 NaN,需要处理
current_mfi = mfi_values[-1]
if np.isnan(current_mfi):
continue
# --- 交易逻辑 ---
# 打印日志方便调试
# print(f"代码: {stock}, 时间: {df.index[-1]}, MFI: {current_mfi:.2f}")
# 1. 卖出逻辑:MFI > 80 (超买)
if current_mfi > ContextInfo.sell_threshold:
# 使用 order_target_percent 调整仓位到 0 (即清仓)
# 这种方式比 passorder 更方便,不用自己计算持仓量
order_target_percent(stock, 0, ContextInfo, ContextInfo.account_id)
# print(f"{stock} 触发卖出信号,MFI={current_mfi}")
# 2. 买入逻辑:MFI < 20 (超卖)
elif current_mfi < ContextInfo.buy_threshold:
# 使用 order_target_percent 调整仓位到 10% (示例:每只股票买入总资产的 10%)
# 注意:实际使用时请根据资金管理需求调整比例
target_percent = 0.1
order_target_percent(stock, target_percent, ContextInfo, ContextInfo.account_id)
# print(f"{stock} 触发买入信号,MFI={current_mfi}")
代码关键点解析
-
# -*- coding: gbk -*-:- QMT 的 Python 编辑器默认编码通常是 GBK,加上这行可以防止中文注释乱码。
-
数据获取 (
get_market_data_ex):- 我们使用了
get_market_data_ex接口,这是 QMT 推荐的高效数据接口。 - 我们需要
high,low,close,volume四个字段来计算 MFI。 count设置为mfi_period * 3是为了确保有足够的数据让talib计算出有效的指标值(去除前期的 NaN 值)。
- 我们使用了
-
指标计算 (
talib.MFI):- QMT 内置了
talib库,直接调用talib.MFI比手动编写公式计算效率更高且更准确。 - 输入数据必须转换为
float类型的numpy数组。
- QMT 内置了
-
交易执行 (
order_target_percent):order_target_percent(stock, percent, ...)是一个非常方便的高级函数。- 当信号为买入时,我们设置目标仓位为
0.1(即 10%)。如果当前没仓位,它会自动买入;如果已有仓位不足 10%,它会补仓。 - 当信号为卖出时,我们设置目标仓位为
0。如果当前有仓位,它会自动卖出平仓。 - 注意:在实盘中,请务必在
init中将ContextInfo.account_id替换为您真实的资金账号。
如何在 QMT 中运行
- 打开 QMT 客户端,进入【模型研究】或【策略交易】界面。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴到编辑器中。
- 修改
init函数中的ContextInfo.account_id为您的实际账号。 - 在右侧参数面板或代码中设置想要交易的股票池。
- 点击【编译】检查是否有语法错误。
- 点击【回测】查看历史表现,或在实盘模式下点击【运行】。
Q&A
Q: 为什么使用 order_target_percent 而不是 passorder?
A: passorder 是最底层的下单函数,需要手动计算买卖数量(例如:当前持仓多少?需要卖多少才能清仓?)。而 order_target_percent 封装了这些逻辑,你只需要告诉它“我希望这只股票占我总资产的百分之几”,系统会自动计算并下单,非常适合这种趋势或反转策略。
Q: 如何调整 MFI 的灵敏度?
A: 修改 init 函数中的 ContextInfo.mfi_period。
- 减小周期(如 10):指标更灵敏,信号更多,但假信号也可能增加。
- 增大周期(如 20):指标更平滑,信号更少,但更可靠。
Q: 实盘交易需要注意什么?
A: 实盘时建议在 handlebar 开头增加 if not ContextInfo.is_last_bar(): return 的判断,确保只在最新的行情数据到来时才进行计算和下单,避免对历史 K 线重复触发交易。同时,请确保已在 QMT 界面登录了交易账号。