顾比均线(Gary Moving Average,GMMA)由交易大师戴若·顾比(Daryl Guppy)提出,是基于多周期移动平均的趋势分析工具。其核心在于通过“短期+长期两组均线群”的分离与粘合状态,来识别市场的趋势方向、强弱以及潜在的反转信号,兼具趋势跟踪和资金行为分析的双重属性。与单一均线相比,顾比均线能更清晰地刻画“短期投机资金”和“长期机构资金”之间的博弈,因此更适用于趋势市以及趋势反转阶段的判断。
以下将从核心原理、实战应用规则、Python完整实现、风险控制四个维度,系统性地拆解顾比均线的落地逻辑。
一、顾比均线核心原理
1. 指标本质:双组均线群的资金行为解读
顾比均线将移动平均线分为“短期组”和“长期组”,分别对应市场中两类不同资金主体的交易行为:
- 短期均线群(3/5/8/10/12/15日):反映短期投机者(如散户、短线游资)的交易行为,对价格波动反应敏感。
- 长期均线群(30/35/40/45/50/60日):反映长期投资者(如机构、主力资金)的持仓成本与行为,趋势更为稳定。
2. 核心计算公式
顾比均线采用指数移动平均(EMA)而非简单移动平均(SMA),因为EMA对近期价格赋予更高权重,能更贴合资金的最新动向。
- 指数移动平均(EMA)公式:
EMA_today = (Price_today * (2/(N+1))) + (EMA_yesterday * (1 - (2/(N+1)))),其中 N 为均线周期。
- 均线分组详情:
| 均线分组 |
周期(日线) |
计算方式 |
资金行为解读 |
| 短期组 |
3, 5, 8, 10, 12, 15 |
各周期EMA值构成均线群 |
短期投机资金的成本区间 |
| 长期组 |
30, 35, 40, 45, 50, 60 |
各周期EMA值构成均线群 |
长期机构资金的成本区间 |
3. 核心判断逻辑
顾比均线的精髓不在于“单根均线的交叉”,而在于观察两组均线群的相对位置关系和整体形态:
| 形态特征 |
资金行为解读 |
趋势判断 |
| 短期组在长期组上方,且两组分离 |
短期资金推动上涨,机构资金锁仓,做多力量强劲 |
多头趋势延续 |
| 短期组在长期组下方,且两组分离 |
短期资金持续抛售,机构资金离场,做空力量强劲 |
空头趋势延续 |
| 两组均线粘合交织 |
多空资金博弈激烈,方向不明 |
震荡市/趋势反转酝酿期 |
| 短期组上穿长期组并开始分离 |
短期资金积极入场,机构资金开始托底 |
多头趋势可能启动 |
| 短期组下穿长期组并开始分离 |
短期资金加速出逃,机构资金开始抛售 |
空头趋势可能启动 |
二、顾比均线实战应用规则
1. 核心交易信号
(1)买入信号(优先级从高到低)
- 趋势启动买入:短期均线群从粘合状态向上突破长期均线群,且长期均线群开始向上倾斜 → 确认多头趋势启动,可考虑分批建仓。
- 回调加仓买入:在明确的多头趋势中,短期均线群回调至长期均线群上方但未下穿,同时成交量出现缩量 → 表明机构资金支撑有效,是加仓时机。
- 震荡转势买入:两组均线长期粘合后,短期组连续3个交易日站稳在长期组上方,且收盘价突破粘合区间上沿 → 预示趋势可能反转,可试探性建仓。
(2)卖出信号(优先级从高到低)
- 趋势反转卖出:短期均线群从粘合状态向下跌破长期均线群,且长期均线群开始向下倾斜 → 确认空头趋势启动,应立即清仓或考虑做空。
- 反弹减仓卖出:在空头趋势中,短期均线群反弹至长期均线群下方但未能上穿,同时成交量异常放大 → 表明压力位有效,是减仓信号。
- 震荡转势卖出:两组均线粘合后,短期组连续3个交易日位于长期组下方,且收盘价跌破粘合区间下沿 → 预示趋势可能向下反转,应果断清仓。
2. 不同市场环境的应用策略
(1)趋势市(核心适配场景)
- 操作逻辑:严格跟随两组均线的分离方向。短期组在长期组上方则坚定持有,在下方则保持空仓。
- 风控设置:将止损位设在长期均线群的下沿(多头趋势)或上沿(空头趋势),避免趋势反转时亏损扩大。
- 示例:当某板块龙头股处于上升趋势时,其短期均线群会始终运行在长期组上方,可持有至短期组明确下穿长期组为止。
(2)震荡市
- 操作逻辑:当两组均线处于粘合状态时,应保持空仓,不参与无序震荡,耐心等待明确的突破信号。
- 过滤机制:当粘合区间的宽度小于近期平均波动幅度的10%时,可判定为震荡市,暂停交易。
- 核心风险:避免在粘合阶段试图“高抛低吸”,容易在真突破时被套。
(3)趋势反转阶段
- 操作逻辑:等待“两组均线出现分离”且得到“成交量确认”(突破时放量),不主观预判。
- 信号验证:突破发生后,需连续2-3个交易日收盘价稳定在突破方向,方可确认反转信号有效,用以过滤假突破。
3. 实战示例(以A股日线数据为例)
| 时间节点 |
均线状态 |
成交量特征 |
交易信号 |
操作动作 |
| 启动初期 |
短期组粘合后上穿长期组,长期组开始上倾 |
突破时放量 |
趋势启动买入 |
建仓30%,止损设于长期组下沿 |
| 趋势中途 |
短期组回调至长期组上方,未下穿 |
回调时缩量 |
回调加仓买入 |
加仓20%,总仓位至50% |
| 趋势延续 |
短期组与长期组分离,同步向上 |
上涨时温和放量 |
持有 |
将止损位上移至最新长期组下沿 |
| 趋势末端 |
短期组下穿长期组,长期组开始下倾 |
下穿时放量下跌 |
趋势反转卖出 |
清仓全部50%仓位 |
三、Python完整实现(含信号生成与可视化)
以下代码完整实现了顾比均线的计算、交易信号生成、策略回测及可视化,适用于A股、期货等日线级别的数据分析。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
# ---------------------- 1. 数据准备(模拟/真实数据源) ----------------------
def get_stock_data(stock_code='300750.SZ', start_date='2022-01-01', end_date='2023-12-31'):
"""
获取股票日线数据(模拟数据,实盘可替换为Tushare/akshare)
:param stock_code: 股票代码
:param start_date: 开始日期
:param end_date: 结束日期
:return: 包含date/high/low/close/volume的DataFrame
"""
# 生成模拟数据(实盘替换为:df = ts.pro_bar(ts_code=stock_code, start_date=start_date, end_date=end_date))
dates = pd.date_range(start=start_date, end=end_date, freq='D')
dates = dates[dates.dayofweek < 5] # 剔除非交易日
n_days = len(dates)
# 模拟股价趋势(先震荡→上涨→下跌)
np.random.seed(42)
base_price = 200
# 第一阶段:震荡(前100天)
trend1 = np.cumsum(np.random.randn(100)*2 - 0.5)[:100] if n_days>100 else np.cumsum(np.random.randn(n_days)*2 - 0.5)
# 第二阶段:上涨(100-200天)
trend2 = np.cumsum(np.random.randn(min(100, n_days-100))*3 + 2)[:min(100, n_days-100)] if n_days>100 else []
# 第三阶段:下跌(200天后)
trend3 = np.cumsum(np.random.randn(max(0, n_days-200))*3 - 3)[:max(0, n_days-200)] if n_days>200 else []
trend = np.concatenate([trend1, trend2, trend3])[:n_days]
close = base_price + trend
high = close + np.random.uniform(0, 10, n_days) # 最高价
low = close - np.random.uniform(0, 10, n_days) # 最低价
volume = np.random.randint(500000, 2000000, n_days) # 成交量
df = pd.DataFrame({
'date': dates,
'high': high,
'low': low,
'close': close,
'volume': volume
})
return df
# ---------------------- 2. 顾比均线计算核心函数 ----------------------
def calculate_guppy_ma(df):
"""
计算顾比均线(短期组+长期组)
:param df: 包含close的DataFrame
:return: 新增顾比均线列的DataFrame
"""
df = df.copy()
# 定义顾比均线周期
short_periods = [3, 5, 8, 10, 12, 15] # 短期均线群
long_periods = [30, 35, 40, 45, 50, 60] # 长期均线群
# 计算短期EMA均线
for period in short_periods:
df[f'ema_short_{period}'] = df['close'].ewm(span=period, adjust=False).mean()
# 计算长期EMA均线
for period in long_periods:
df[f'ema_long_{period}'] = df['close'].ewm(span=period, adjust=False).mean()
# 计算短期/长期均线群的均值(简化趋势判断)
df['short_ma_mean'] = df[[f'ema_short_{p}' for p in short_periods]].mean(axis=1)
df['long_ma_mean'] = df[[f'ema_long_{p}' for p in long_periods]].mean(axis=1)
# 计算均线群分离度(反映趋势强弱:分离度越大,趋势越强)
df['separation'] = (df['short_ma_mean'] - df['long_ma_mean']) / df['long_ma_mean'] * 100
return df
# ---------------------- 3. 交易信号生成 ----------------------
def generate_trade_signals(df):
"""
基于顾比均线生成买卖信号
:param df: 包含顾比均线列的DataFrame
:return: 新增信号列的DataFrame
"""
df = df.copy()
df['signal'] = 0 # 0=无信号,1=买入,-1=卖出
df['position'] = 0 # 持仓状态:0=空仓,1=持仓
# 定义判断条件
# 1. 趋势启动买入:短期均值上穿长期均值,且长期均值向上
df['long_ma_up'] = df['long_ma_mean'] > df['long_ma_mean'].shift(1) # 长期均线向上
df['short_cross_long'] = (df['short_ma_mean'] > df['long_ma_mean']) & (df['short_ma_mean'].shift(1) <= df['long_ma_mean'].shift(1)) # 短期上穿长期
buy_cond1 = df['short_cross_long'] & df['long_ma_up']
# 2. 回调加仓买入:多头持仓中,短期均值回调至长期均值上方,缩量
df['volume_ma20'] = df['volume'].rolling(window=20).mean()
df['volume_shrink'] = df['volume'] < 0.8 * df['volume_ma20'] # 缩量
df['short_above_long'] = df['short_ma_mean'] > df['long_ma_mean'] # 短期在长期上方
buy_cond2 = (df['position'] == 1) & df['short_above_long'] & df['volume_shrink']
# 3. 趋势反转卖出:短期均值下穿长期均值,且长期均值向下
df['long_ma_down'] = df['long_ma_mean'] < df['long_ma_mean'].shift(1) # 长期均线向下
df['short_cross_long_down'] = (df['short_ma_mean'] < df['long_ma_mean']) & (df['short_ma_mean'].shift(1) >= df['long_ma_mean'].shift(1)) # 短期下穿长期
sell_cond1 = df['short_cross_long_down'] & df['long_ma_down']
# 4. 反弹减仓卖出:空头趋势中,短期均值反弹至长期均值下方,放量
df['volume_expand'] = df['volume'] > 1.2 * df['volume_ma20'] # 放量
df['short_below_long'] = df['short_ma_mean'] < df['long_ma_mean'] # 短期在长期下方
sell_cond2 = (df['position'] == 1) & df['short_below_long'] & df['volume_expand']
# 赋值信号(避免重复信号)
df.loc[buy_cond1 | buy_cond2, 'signal'] = 1
df.loc[sell_cond1 | sell_cond2, 'signal'] = -1
# 计算持仓状态(信号触发后持仓,直到反向信号)
for i in range(1, len(df)):
if df['signal'].iloc[i] == 1:
df['position'].iloc[i] = 1
elif df['signal'].iloc[i] == -1:
df['position'].iloc[i] = 0
else:
df['position'].iloc[i] = df['position'].iloc[i-1]
return df
# ---------------------- 4. 策略回测 ----------------------
def backtest_strategy(df):
"""
回测顾比均线策略收益
:param df: 含信号和持仓的DataFrame
:return: 回测结果字典
"""
# 计算每日收益率
df['daily_return'] = df['close'].pct_change()
# 策略收益率(仅持仓时收益)
df['strategy_return'] = df['daily_return'] * df['position'].shift(1)
# 累计收益率
df['cum_market_return'] = (1 + df['daily_return']).cumprod() - 1
df['cum_strategy_return'] = (1 + df['strategy_return']).cumprod() - 1
# 回测指标
total_trades = len(df[df['signal'] != 0])
total_return = df['cum_strategy_return'].iloc[-1]
annual_return = (1 + total_return) ** (252 / len(df)) - 1 # 年化收益率
max_drawdown = (df['cum_strategy_return'] - df['cum_strategy_return'].cummax()).min() # 最大回撤
win_rate = len(df[(df['strategy_return'] > 0) & (df['position'].shift(1) == 1)]) / len(df[df['position'].shift(1) == 1]) if len(df[df['position'].shift(1) == 1])>0 else 0
return {
'总交易次数': total_trades,
'总收益率': round(total_return * 100, 2),
'年化收益率': round(annual_return * 100, 2),
'最大回撤': round(max_drawdown * 100, 2),
'胜率': round(win_rate * 100, 2)
}
# ---------------------- 5. 可视化顾比均线 ----------------------
def plot_guppy_ma(df):
"""
绘制顾比均线+股价+交易信号+分离度
"""
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示
plt.rcParams['axes.unicode_minus'] = False
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(18, 12))
# 子图1:股价+顾比均线群
ax1.plot(df['date'], df['close'], label='收盘价', color='black', linewidth=1.5)
# 绘制短期均线群(红色系,浅色调)
short_periods = [3, 5, 8, 10, 12, 15]
colors_short = ['#FF6B6B', '#FF8E8E', '#FFB3B3', '#FFD1D1', '#FFE8E8', '#FFF2F2']
for i, period in enumerate(short_periods):
ax1.plot(df['date'], df[f'ema_short_{period}'], color=colors_short[i], linewidth=1, alpha=0.7, label=f'短期{period}日' if i==0 else "")
# 绘制长期均线群(绿色系,深色调)
long_periods = [30, 35, 40, 45, 50, 60]
colors_long = ['#006400', '#228B22', '#32CD32', '#90EE90', '#98FB98', '#F0FFF0']
for i, period in enumerate(long_periods):
ax1.plot(df['date'], df[f'ema_long_{period}'], color=colors_long[i], linewidth=1, alpha=0.7, label=f'长期{period}日' if i==0 else "")
# 标记买入/卖出信号
buy_signals = df[df['signal'] == 1]
sell_signals = df[df['signal'] == -1]
ax1.scatter(buy_signals['date'], buy_signals['close'], marker='^', color='red', s=100, label='买入信号')
ax1.scatter(sell_signals['date'], sell_signals['close'], marker='v', color='green', s=100, label='卖出信号')
ax1.set_title('顾比均线群与股价', fontsize=14)
ax1.set_ylabel('价格(元)', fontsize=12)
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3)
# 子图2:均线群均值+分离度
ax2.plot(df['date'], df['short_ma_mean'], label='短期均线均值', color='red', linewidth=1.5)
ax2.plot(df['date'], df['long_ma_mean'], label='长期均线均值', color='green', linewidth=1.5)
ax2_twin = ax2.twinx()
ax2_twin.plot(df['date'], df['separation'], label='分离度(%)', color='blue', linewidth=1, linestyle='--')
ax2.set_title('短期/长期均线均值与分离度', fontsize=14)
ax2.set_ylabel('均线均值(元)', fontsize=12)
ax2_twin.set_ylabel('分离度(%)', fontsize=12)
ax2.legend(loc='upper left')
ax2_twin.legend(loc='upper right')
ax2.grid(True, alpha=0.3)
# 子图3:持仓状态
ax3.fill_between(df['date'], 0, df['position'], color='gray', alpha=0.5, label='持仓状态(1=持仓,0=空仓)')
ax3.set_title('策略持仓状态', fontsize=14)
ax3.set_ylabel('持仓状态', fontsize=12)
ax3.legend(loc='upper left')
ax3.grid(True, alpha=0.3)
# 子图4:累计收益率对比
ax4.plot(df['date'], df['cum_strategy_return'] * 100, label='策略累计收益', color='red', linewidth=1.5)
ax4.plot(df['date'], df['cum_market_return'] * 100, label='市场累计收益', color='blue', linewidth=1.5)
ax4.set_title('策略vs市场累计收益率', fontsize=14)
ax4.set_xlabel('日期', fontsize=12)
ax4.set_ylabel('累计收益率(%)', fontsize=12)
ax4.legend(loc='upper left')
ax4.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# ---------------------- 主函数调用 ----------------------
if __name__ == "__main__":
# 步骤1:获取数据
stock_df = get_stock_data(stock_code='300750.SZ', start_date='2022-01-01', end_date='2023-12-31')
# 步骤2:计算顾比均线
guppy_df = calculate_guppy_ma(stock_df)
# 步骤3:生成交易信号
signal_df = generate_trade_signals(guppy_df)
# 步骤4:回测策略
backtest_result = backtest_strategy(signal_df)
print("顾比均线策略回测结果:")
for key, value in backtest_result.items():
print(f"{key}:{value}%")
# 步骤5:可视化
plot_guppy_ma(signal_df)
# 输出关键数据
print("\n核心数据(最后10行):")
print(signal_df[['date', 'close', 'short_ma_mean', 'long_ma_mean', 'separation', 'signal', 'position']].tail(10))
代码关键说明
- 核心计算:使用Pandas的
ewm(span=period, adjust=False)方法计算指数移动平均(EMA),严格遵循顾比均线的原始定义。
- 简化判断:通过计算短期组和长期组所有均线的平均值,简化了多线分析的复杂度,同时保留了整体趋势特征。
- 信号过滤:策略结合成交量特征(缩量回调、放量反弹)对买卖信号进行过滤,有助于提升策略的胜率,这是在量化交易实践中常用的技巧。
- 可视化呈现:通过4个子图分别展示股价与均线群、均线均值与分离度、策略持仓状态以及累计收益对比,能够直观、全面地评估策略效果。整个实现过程充分展示了如何利用Python进行金融数据分析与策略开发。
四、参数优化与风险控制
1. 周期参数优化(适配不同标的与市场周期)
顾比均线的经典周期参数通常无需大幅调整,但可根据交易标的的波动特性进行微调,以优化灵敏度与稳定性的平衡:
| 标的类型 |
短期周期调整建议 |
长期周期调整建议 |
核心逻辑 |
| 低波动标的(如银行股) |
5, 8, 10, 12, 15, 20 |
40, 45, 50, 55, 60, 70 |
扩大周期,降低噪声干扰,提高信号稳定性 |
| 中波动标的(如消费股) |
3, 5, 8, 10, 12, 15 |
30, 35, 40, 45, 50, 60 |
使用经典参数,在灵敏度与稳定性间取得平衡 |
| 高波动标的(如科技股) |
2, 3, 5, 8, 10, 12 |
25, 30, 35, 40, 45, 50 |
适当缩小周期,以更快捕捉短期趋势变化 |
| 高频波动标的(如期货) |
1, 2, 3, 5, 8, 10 |
15, 20, 25, 30, 35, 40 |
适配高波动性,要求指标能快速响应趋势 |
2. 核心风险点与应对方案
| 风险点 |
应对方案 |
| 震荡市中的假突破信号 |
增加“分离度过滤”:仅当分离度大于1%(多头)或小于-1%(空头)时才执行交易;分离度过小时保持空仓。 |
| 趋势反转时止损不及时 |
采用“分离度止损”法:在多头持仓中,一旦分离度由正转负,立即止损;空头持仓则反之。 |
| 极端行情下均线滞后 |
叠加其他趋势指标:例如结合唐奇安通道(Donchian Channel),仅当顾比均线信号与通道突破信号共振时才交易。 |
| 单一指标信号胜率有限 |
构建多指标共振系统:结合MACD的金叉/死叉、RSI的超买/超卖状态来共同确认趋势方向。 |
3. 进阶优化技巧
(1)分离度动态阈值
不要使用固定的1%作为阈值。可以根据标的历史数据中分离度的分布情况,设置动态阈值。例如,取历史分离度数据的90%分位数(如2%),那么仅当当前分离度大于2%时才执行加仓操作。
(2)基于分离度的仓位管理
将仓位大小与趋势强度(分离度)挂钩,实现动态仓位管理:
- 分离度 < 1%(弱趋势):轻仓试探,仓位20%-30%。
- 1% ≤ 分离度 ≤ 3%(中趋势):标准仓位,50%-60%。
- 分离度 > 3%(强趋势):重仓参与,70%-80%。
(3)均线粘合度量化过滤
通过计算短期组或长期组内各条均线的标准差,来量化“粘合”程度。当该标准差小于近期股价波动范围的0.5%时,可判定市场处于高度粘合的震荡市,此时应空仓等待,避免无效交易。
五、总结:顾比均线的核心价值与适用边界
1. 核心价值
- 解读资金行为:直观区分短期投机资金和长期机构资金的动向,有助于判断趋势的可持续性。
- 刻画趋势特征:均线群的“分离”与“粘合”形态,比单根均线能更有效地反映趋势的强弱和转换。
- 规则清晰可执行:基于均线群相对位置的买卖信号逻辑清晰,易于理解和程序化执行。
2. 适用边界
- 适合场景:趋势性明确的标的(如行业龙头、大盘指数ETF)、日线或周线等中长期图表、侧重于趋势跟踪的交易者。
- 不适合场景:长期处于震荡行情的标的(如部分小盘股)、分钟级别的超短线交易(均线滞后性会导致信号无效)。
3. 实战口诀
- 短长分离看趋势,短上长多短下空。
- 粘合震荡不交易,分离突破才入场。
- 缩量回调可加仓,放量反弹要减仓。
- 分离度定趋势强,风控永远放第一。
顾比均线的本质是 “通过多周期均线群来观测和解读市场资金的多空博弈” 。在实战中,不必过分关注单根均线的细微变化,而应将重点放在两组均线的整体形态关系上。结合成交量验证与分离度过滤,方能在趋势行情中最大化利润,在震荡行情中保存实力。
风险提示:本文内容仅作为技术分析方法分享与Python编程学习之用,所涉及的策略回测基于模拟数据,不构成任何实际的投资建议。金融市场有风险,投资需谨慎。
|