问题描述
解决方案
威廉姆斯鳄鱼线(Williams Alligator)是一种基于趋势跟随的交易策略,利用三条平滑移动平均线(SMMA)及其向未来的位移来判断市场的趋势和盘整状态。
策略原理
鳄鱼线由三条线组成:
- 鳄鱼下颚 (Jaw - 蓝线):13 周期 SMMA(平滑移动平均),向未来位移 8 根 K 线。
- 鳄鱼牙齿 (Teeth - 红线):8 周期 SMMA,向未来位移 5 根 K 线。
- 鳄鱼嘴唇 (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')
代码关键点解析
-
SMMA 计算:
- 威廉姆斯指标使用的是平滑移动平均线(Smoothed Moving Average)。
- 在 Pandas 中,
ewm(alpha=1/N)等价于 SMMA。代码中get_smma函数实现了这一点。 - 计算基础是
(High + Low) / 2(中价),而不是收盘价。
-
位移逻辑 (Shift):
- 这是鳄鱼线最容易出错的地方。指标定义是“向未来位移”。
- 在实时交易或回测中,这意味着当前时刻(Time T)的鳄鱼线数值,实际上是过去(Time T-Shift)计算出来的均线值。
- 代码中使用
iloc[-1 - shift]来获取当前时刻生效的指标值。
-
交易信号:
- 买入:当三线呈多头排列(绿>红>蓝)且收盘价站上绿线时,视为趋势爆发。
- 卖出:当绿线下穿红线,或者价格跌破红线(牙齿)时,视为趋势结束或进入盘整。
-
数据获取:
- 使用了
get_market_data_ex,这是 QMT 推荐的高效数据接口。 - 设置了
dividend_type='front'(前复权),这对股票策略回测至关重要,防止分红除权导致的价格跳空误触止损。
- 使用了
如何使用
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 修改
init函数中的ContextInfo.account_id为你的实际账号。 - 点击【运行】或【回测】。
常见问题 Q&A
Q: 为什么图表上画出的线和通达信/同花顺的不完全一样?
A: 主要是因为 SMMA 的初始值计算方式不同。有的软件第一笔用 SMA(简单平均)启动,有的直接用 EMA。随着数据量的增加,这种差异会迅速缩小。另外,请确保对比的是“中价”计算的均线,而不是“收盘价”。
Q: 这个策略可以直接实盘吗?
A: 代码中使用了 order_value 和 order_target_value,这些是封装好的简易交易函数。在实盘中,建议结合 get_trade_detail_data 查询实际资金和持仓,并使用 passorder 函数以获得更精细的控制(如限价单、撤单逻辑等)。此外,鳄鱼线在震荡市中容易产生假信号,建议增加过滤器(如结合分形指标 Fractals)。
Q: 如何做空?
A: 如果交易标的是期货(如 IF, RB 等),可以在 sell_signal 触发时使用 passorder 发送开空指令(opType=3),在 buy_signal 触发时平空开多。上述代码主要针对 A 股做多逻辑编写。