🚀 AI 一键生成 qmt 策略代码

威廉姆斯鳄鱼线趋势策略 Python 编写教程

qmt | 发布时间: 2025/12/23
以下内容由 EasyQuant 生成。

问题描述

怎么编写威廉姆斯鳄鱼线 (Alligator) 交易策略?

解决方案

威廉姆斯鳄鱼线(Williams Alligator)是一种基于趋势跟随的交易策略,利用三条平滑移动平均线(SMMA)及其向未来的位移来判断市场的趋势和盘整状态。

策略原理

鳄鱼线由三条线组成:

  1. 鳄鱼下颚 (Jaw - 蓝线):13 周期 SMMA(平滑移动平均),向未来位移 8 根 K 线。
  2. 鳄鱼牙齿 (Teeth - 红线):8 周期 SMMA,向未来位移 5 根 K 线。
  3. 鳄鱼嘴唇 (Lips - 绿线):5 周期 SMMA,向未来位移 3 根 K 线。

交易逻辑:

  • 多头信号(张嘴进食):嘴唇 > 牙齿 > 下颚(绿 > 红 > 蓝),且价格高于嘴唇。
  • 平仓/止损:当价格跌破牙齿或嘴唇下穿牙齿时平仓。
  • 盘整(睡觉):三条线纠缠在一起,不进行交易。

QMT 策略代码实现

以下是完整的 Python 策略代码。该策略计算中价 (High+Low)/2 的 SMMA,并处理了向未来的位移逻辑(即当前时刻对比的是过去计算出的均线值)。

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 设置策略运行的股票列表 (示例:平安银行)
    ContextInfo.stock_code = '000001.SZ'
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 设置资金账号 (请替换为您的真实资金账号)
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
    ContextInfo.account_type = 'STOCK' # 股票账户
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 策略参数设置
    ContextInfo.jaw_period = 13    # 下颚周期
    ContextInfo.jaw_shift = 8      # 下颚位移
    ContextInfo.teeth_period = 8   # 牙齿周期
    ContextInfo.teeth_shift = 5    # 牙齿位移
    ContextInfo.lips_period = 5    # 嘴唇周期
    ContextInfo.lips_shift = 3     # 嘴唇位移
    
    # 每次交易的仓位比例
    ContextInfo.trade_ratio = 0.5 

def get_smma(series, period):
    """
    计算平滑移动平均线 (SMMA)
    SMMA 等同于 alpha = 1/period 的 EMA
    """
    return series.ewm(alpha=1/period, adjust=False).mean()

def handlebar(ContextInfo):
    """
    K线周期回调函数
    """
    # 获取当前正在处理的股票代码
    stock = ContextInfo.stock_code
    
    # 获取当前K线索引
    index = ContextInfo.barpos
    
    # 获取历史行情数据
    # 我们需要足够的数据来计算均线和处理位移,这里取 100 根
    data_len = 100
    
    # 使用 get_market_data_ex 获取数据 (推荐方式)
    # 注意:end_time 不传默认为当前最新时间,count 控制获取数量
    market_data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [stock], 
        period=ContextInfo.period, 
        count=data_len,
        dividend_type='front' # 前复权
    )
    
    if stock not in market_data:
        return
        
    df = market_data[stock]
    
    # 数据长度不足时不计算
    if len(df) < 30:
        return

    # 1. 计算中价 (Median Price) = (High + Low) / 2
    df['median_price'] = (df['high'] + df['low']) / 2
    
    # 2. 计算三条 SMMA 均线
    # 注意:这里计算的是原始序列,还没有进行位移
    df['jaw_raw'] = get_smma(df['median_price'], ContextInfo.jaw_period)
    df['teeth_raw'] = get_smma(df['median_price'], ContextInfo.teeth_period)
    df['lips_raw'] = get_smma(df['median_price'], ContextInfo.lips_period)
    
    # 3. 获取当前时刻对应的鳄鱼线数值
    # 鳄鱼线的定义是“向未来位移”,这意味着:
    # 当前时刻看到的“下颚值”,实际上是 (当前时间 - 8) 时刻计算出来的 SMMA 值
    # 当前时刻看到的“牙齿值”,实际上是 (当前时间 - 5) 时刻计算出来的 SMMA 值
    # 当前时刻看到的“嘴唇值”,实际上是 (当前时间 - 3) 时刻计算出来的 SMMA 值
    
    try:
        # iloc[-1] 是当前最新 bar
        # iloc[-1 - shift] 是回溯 shift 根 bar 的数据
        current_jaw = df['jaw_raw'].iloc[-1 - ContextInfo.jaw_shift]
        current_teeth = df['teeth_raw'].iloc[-1 - ContextInfo.teeth_shift]
        current_lips = df['lips_raw'].iloc[-1 - ContextInfo.lips_shift]
        
        current_close = df['close'].iloc[-1]
        
        # 上一根K线的数据(用于判断交叉)
        last_jaw = df['jaw_raw'].iloc[-2 - ContextInfo.jaw_shift]
        last_teeth = df['teeth_raw'].iloc[-2 - ContextInfo.teeth_shift]
        last_lips = df['lips_raw'].iloc[-2 - ContextInfo.lips_shift]
        
    except IndexError:
        # 如果历史数据不够回溯,则跳过
        return

    # 4. 获取当前持仓
    positions = ContextInfo.get_position()
    long_pos = 0
    for pos in positions:
        if pos.m_strInstrumentID == stock:
            long_pos = pos.m_nVolume
            break

    # 5. 生成交易信号
    
    # 多头排列条件:嘴唇 > 牙齿 > 下颚 (向上张嘴)
    is_bullish_alignment = (current_lips > current_teeth) and (current_teeth > current_jaw)
    
    # 买入信号:多头排列 且 价格在嘴唇上方
    buy_signal = is_bullish_alignment and (current_close > current_lips)
    
    # 卖出信号:嘴唇下穿牙齿 (趋势减弱或反转) 或 价格跌破牙齿
    sell_signal = (current_lips < current_teeth) or (current_close < current_teeth)

    # 6. 执行交易
    
    # 开仓逻辑
    if buy_signal and long_pos == 0:
        # 计算目标持仓价值 (总资产 * 比例)
        # 注意:这里简单演示,实盘需考虑可用资金判断
        # order_target_percent 需要在回测模式或正确配置总资产后使用
        # 这里使用 order_value 演示买入固定金额,例如 50000 元
        # 实盘建议使用 passorder 配合 get_trade_detail_data 做更细致的风控
        
        print(f"时间: {df.index[-1]}, 触发买入: 价格={current_close}, 嘴唇={current_lips}, 牙齿={current_teeth}, 下颚={current_jaw}")
        order_value(stock, 50000, ContextInfo, ContextInfo.account_id)
        
    # 平仓逻辑
    elif sell_signal and long_pos > 0:
        print(f"时间: {df.index[-1]}, 触发卖出: 价格={current_close}, 嘴唇={current_lips}, 牙齿={current_teeth}")
        # 卖出所有持仓
        order_target_value(stock, 0, ContextInfo, ContextInfo.account_id)

    # 7. (可选) 在图表上画线,方便回测观察
    # 注意:paint 画图是画在当前 bar 上的,为了视觉上符合鳄鱼线定义,
    # 我们画图时画的是当前时刻生效的值(即已经位移过的值)
    ContextInfo.paint('Jaw', current_jaw, -1, 0, 'blue')
    ContextInfo.paint('Teeth', current_teeth, -1, 0, 'red')
    ContextInfo.paint('Lips', current_lips, -1, 0, 'green')

代码关键点解析

  1. SMMA 计算

    • 威廉姆斯指标使用的是平滑移动平均线(Smoothed Moving Average)。
    • 在 Pandas 中,ewm(alpha=1/N) 等价于 SMMA。代码中 get_smma 函数实现了这一点。
    • 计算基础是 (High + Low) / 2(中价),而不是收盘价。
  2. 位移逻辑 (Shift)

    • 这是鳄鱼线最容易出错的地方。指标定义是“向未来位移”。
    • 在实时交易或回测中,这意味着当前时刻(Time T)的鳄鱼线数值,实际上是过去(Time T-Shift)计算出来的均线值。
    • 代码中使用 iloc[-1 - shift] 来获取当前时刻生效的指标值。
  3. 交易信号

    • 买入:当三线呈多头排列(绿>红>蓝)且收盘价站上绿线时,视为趋势爆发。
    • 卖出:当绿线下穿红线,或者价格跌破红线(牙齿)时,视为趋势结束或进入盘整。
  4. 数据获取

    • 使用了 get_market_data_ex,这是 QMT 推荐的高效数据接口。
    • 设置了 dividend_type='front'(前复权),这对股票策略回测至关重要,防止分红除权导致的价格跳空误触止损。

如何使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 修改 init 函数中的 ContextInfo.account_id 为你的实际账号。
  5. 点击【运行】或【回测】。

常见问题 Q&A

Q: 为什么图表上画出的线和通达信/同花顺的不完全一样?
A: 主要是因为 SMMA 的初始值计算方式不同。有的软件第一笔用 SMA(简单平均)启动,有的直接用 EMA。随着数据量的增加,这种差异会迅速缩小。另外,请确保对比的是“中价”计算的均线,而不是“收盘价”。

Q: 这个策略可以直接实盘吗?
A: 代码中使用了 order_valueorder_target_value,这些是封装好的简易交易函数。在实盘中,建议结合 get_trade_detail_data 查询实际资金和持仓,并使用 passorder 函数以获得更精细的控制(如限价单、撤单逻辑等)。此外,鳄鱼线在震荡市中容易产生假信号,建议增加过滤器(如结合分形指标 Fractals)。

Q: 如何做空?
A: 如果交易标的是期货(如 IF, RB 等),可以在 sell_signal 触发时使用 passorder 发送开空指令(opType=3),在 buy_signal 触发时平空开多。上述代码主要针对 A 股做多逻辑编写。