手把手教你用QMT Python API和TA-Lib构建双均线自动化交易策略
在量化交易领域,原始的行情数据(如开盘价、收盘价、成交量)本身并不能直接指导交易决策。我们需要借助有效的工具将这些基础数据转化为可执行的买卖信号,而技术指标正是实现这一转化的核心手段。
技术指标通过对价格、成交量等原始数据进行特定数学运算,提炼出反映市场趋势、动能、波动性等深层特征的信息。例如,移动平均线(MA)、相对强弱指数(RSI)、指数平滑异同移动平均线(MACD)以及布林带(Bollinger Bands)等都是广为应用的分析工具。
本文将以经典的双均线交叉策略为例,详细演示如何在QMT量化平台中,利用TA-Lib库计算技术指标,并基于指标生成完整的交易信号。跟随本教程,你将能够构建并回测一个基础的自动化交易策略。
一、 从数据到信号:技术指标的桥梁作用
在量化策略的构建流程中,技术指标承担着将市场价格信息“翻译”成交易指令的关键角色。它们帮助交易者识别肉眼难以察觉的价格模式与市场状态,为系统化决策提供客观依据。
二、 技术指标计算的利器:TA-Lib库
TA-Lib(Technical Analysis Library)是一个专为技术分析设计的高性能开源函数库,它提供了超过150种技术指标的计算方法。该库以计算速度快、接口统一规范著称,是量化开发者不可或缺的工具。
值得庆幸的是,QMT内置的Python环境已预装TA-Lib,开发者无需进行额外安装,可直接在策略代码中导入使用:
import talib
常用技术指标函数速查表
| 指标类型 | 函数名 | 核心功能 |
|---|---|---|
| 简单移动平均线 | talib.SMA |
计算指定周期的算术平均价格 |
| 指数移动平均线 | talib.EMA |
计算加权平均,近期价格权重更高 |
| 指数平滑异同平均线 | talib.MACD |
返回DIF线、DEA信号线及MACD柱 |
| 相对强弱指数 | talib.RSI |
衡量价格变动速度与幅度,范围0-100 |
| 布林带 | talib.BBANDS |
计算价格通道的上轨、中轨与下轨 |
| 平均真实波幅 | talib.ATR |
衡量市场波动性,常用于设置止损 |
| KDJ指标 | 需组合计算 | QMT未直接提供,可依据公式自行实现 |
三、 双均线策略:趋势跟踪的经典范例
双均线交叉策略因其逻辑直观、易于实现,成为量化入门者的首选。其核心规则非常简单:
- 买入信号:当短期均线由下向上穿越长期均线,形成“金叉”。
- 卖出信号:当短期均线由上向下穿越长期均线,形成“死叉”。
该策略的本质是捕捉并跟随市场的中期趋势。一旦趋势确立,均线的多头或空头排列通常会持续一段时间。
策略参数设定
- 短期均线周期:常设为5、10或20日。
- 长期均线周期:常设为20、60或120日。
下文将以5日均线(快线)和20日均线(慢线)的组合为例,构建完整的策略逻辑。
四、 策略构建分步详解
第一步:获取历史行情数据
在QMT中,可使用 get_market_data 函数获取指定品种的历史K线数据。为确保计算5日和20日均线有足够的数据,我们至少需要获取20根K线。为稳妥起见,通常获取更多数据(如30根)。
def handlebar(ContextInfo):
# 获取当前策略运行的主合约代码
stock_code = ContextInfo.stockcode
# 获取最近30个交易日的日线收盘价数据
data = ContextInfo.get_market_data(
fields=['close'],
stock_code=[stock_code],
period='1d',
count=30
)
# 若获取的数据量不足,则直接返回,不进行后续计算
if data is None or len(data) < 20:
return
# 提取收盘价序列,转换为numpy数组供TA-Lib使用
closes = data['close'].values
注意:get_market_data 返回一个 pandas.DataFrame 对象。当仅查询单一标的时,可直接通过字段名(如 'close')获取数据列。
第二步:计算移动平均线
使用TA-Lib的 SMA 函数计算简单移动平均线。该函数返回一个与输入数据等长的数组,前 n-1 个值(n为周期)为 NaN(非数字),需取末尾的有效值进行判断。
# 计算5日与20日简单移动平均线
ma5 = talib.SMA(closes, timeperiod=5)
ma20 = talib.SMA(closes, timeperiod=20)
# 获取最新两根K线对应的均线值
current_ma5 = ma5[-1]
current_ma20 = ma20[-1]
prev_ma5 = ma5[-2]
prev_ma20 = ma20[-2]
# 判断是否出现金叉或死叉
# 金叉条件:前一日快线<=慢线,且当日快线>慢线
golden_cross = (prev_ma5 <= prev_ma20) and (current_ma5 > current_ma20)
# 死叉条件:前一日快线>=慢线,且当日快线<慢线
death_cross = (prev_ma5 >= prev_ma20) and (current_ma5 < current_ma20)
第三步:生成并执行交易信号
基于金叉/死叉信号,结合当前持仓状态,决定是否发出交易指令。这里通过 ContextInfo 对象的自定义属性来跨K线记录持仓状态。
# 获取全局持仓状态(在init函数中初始化)
has_position = getattr(ContextInfo, 'has_position', False)
if golden_cross and not has_position:
# 触发买入信号
print(f'金叉出现,生成买入信号')
ContextInfo.has_position = True
# 此处可插入实际下单代码
elif death_cross and has_position:
# 触发卖出信号
print(f'死叉出现,生成卖出信号')
ContextInfo.has_position = False
# 此处可插入实际下单代码
五、 完整的双均线策略代码
将初始化、数据获取、指标计算、信号生成与订单管理模块整合,便得到一个可直接在QMT中运行的回测策略。
# coding:gbk
import talib
def init(ContextInfo):
# 设置交易使用的资金账户(需替换为实盘账号)
ContextInfo.set_account('你的资金账号')
# 初始化持仓状态标志
ContextInfo.has_position = False
def handlebar(ContextInfo):
# 仅在全推模式下最后一根确定的K线处理信号,避免在历史K线上产生信号
if not ContextInfo.is_last_bar():
return
stock_code = ContextInfo.stockcode
if not stock_code:
return
# 获取30根日线收盘价
data = ContextInfo.get_market_data(
fields=['close'],
stock_code=[stock_code],
period='1d',
count=30
)
if data is None or len(data) < 20:
return
closes = data['close'].values
# 计算均线
ma5 = talib.SMA(closes, timeperiod=5)
ma20 = talib.SMA(closes, timeperiod=20)
# 获取最新两个有效值
current_ma5 = ma5[-1]
current_ma20 = ma20[-1]
prev_ma5 = ma5[-2]
prev_ma20 = ma20[-2]
# 判断金叉死叉
golden_cross = (prev_ma5 <= prev_ma20) and (current_ma5 > current_ma20)
death_cross = (prev_ma5 >= prev_ma20) and (current_ma5 < current_ma20)
has_position = ContextInfo.has_position
# 生成并执行信号
if golden_cross and not has_position:
# 买入信号:以市价买入1000股(10手)
order_lots(stock_code, 10, ContextInfo, ContextInfo.get_account()) # A股1手为100股
ContextInfo.has_position = True
print(f'{stock_code} 金叉买入')
elif death_cross and has_position:
# 卖出信号:卖出1000股(此处示例为固定数量,实战中应查询实际持仓)
order_lots(stock_code, -10, ContextInfo, ContextInfo.get_account())
ContextInfo.has_position = False
print(f'{stock_code} 死叉卖出')
注:示例中的 order_lots 函数使用固定手数进行交易。在实际策略中,更严谨的做法是根据账户资金、风险比例或查询到的实际持仓数量来确定委托量。
六、 策略回测实践
在QMT策略编辑器中运行上述代码,可以进行历史回测以评估策略表现:
- 将代码复制到策略编辑器。
- 在“基本信息”中设置测试标的(例如某只股票或指数)。
- 在“回测参数”中配置测试时间范围、初始资金(如10万元)。
- 点击“回测”按钮运行。
- 分析回测报告,关注总收益率、夏普比率、最大回撤、胜率、交易次数等关键指标。
通过回测你可能会发现,双均线策略在单边趋势行情中表现优异,但在震荡盘整市中容易产生连续亏损。这正是量化策略开发的起点:发现问题并着手优化。
七、 策略进阶:多指标协同过滤
为了提升单一均线策略的稳定性,降低虚假信号的干扰,可以引入其他技术指标构成过滤条件。
1. 结合RSI指标过滤超买超卖
在金叉出现时,若RSI处于超卖区,则买入信号更强;在死叉出现时,若RSI处于超买区,则卖出信号更可信。
# 计算14日RSI
rsi = talib.RSI(closes, timeperiod=14)
current_rsi = rsi[-1]
# 增强的买入条件:金叉且RSI低于30(超卖)
if golden_cross and not has_position and current_rsi < 30:
# 执行买入
pass
2. 结合布林带判断价格位置
当金叉出现在布林带下轨附近时,可能是较好的抄底机会;当死叉出现在上轨附近时,可能是较强的见顶信号。
# 计算布林带
upper, middle, lower = talib.BBANDS(closes, timeperiod=20)
current_close = closes[-1]
current_lower = lower[-1]
# 增强条件:金叉且收盘价位于布林带下轨附近(如2%范围内)
if golden_cross and not has_position and current_close <= current_lower * 1.02:
# 执行买入
pass
3. 添加成交量确认
价升量增是趋势健康的表现。在金叉当日,若成交量显著放大,可增强信号可靠性。
# 获取成交量数据
vol_data = ContextInfo.get_market_data(
fields=['volume'],
stock_code=[stock_code],
period='1d',
count=30
)
volumes = vol_data['volume'].values
current_vol = volumes[-1]
avg_vol = talib.SMA(volumes, timeperiod=20)[-1] # 20日平均成交量
# 增强条件:金叉且当日成交量超过均量的50%
if golden_cross and not has_position and current_vol > avg_vol * 1.5:
# 执行买入
pass
八、 关于信号有效性的重要提醒
在QPT的回测机制中,handlebar函数会对历史每一根K线进行运算,交易函数会被记录。而在实盘模式下,handlebar会随着行情跳动(tick)被频繁调用。
为确保交易信号在实盘中的有效性,避免在K线未定型时发出委托,建议始终将核心交易逻辑包裹在 if ContextInfo.is_last_bar(): 条件判断内。这能保证只在最新一根已确定的K线收盘后处理信号,符合大多数趋势策略的逻辑。
九、 结语:从简单策略出发,持续迭代优化
本文通过构建一个基础的双均线交叉策略,完整展示了在QMT平台中使用Python和TA-Lib进行量化策略开发的核心流程:数据获取 -> 指标计算 -> 信号生成 -> 订单执行与仓位管理。
这个简单的策略模型是一个强大的学习工具和迭代起点。在后续的探讨中,我们将深入策略回测的深度分析、绩效评估的维度,以及如何通过参数优化、风险控制等手段提升策略的稳健性与适应性。同时,我们也将警惕过度优化带来的“过拟合”风险,追求策略在未来未知市场中的泛化能力。
动手实践是学习量化的最佳途径。如果你已经跟随本文完成了第一个策略的构建与回测,那么恭喜你,已经成功踏入了自动化交易的大门。记住,所有复杂的量化系统都始于一个简单的想法和一行行代码的积累。