
欧奈尔杯柄形态到底长什么样
杯柄形态是威廉·欧奈尔在CANSLIM体系中提出的经典看涨中继形态。它像一只带把手的茶杯:股价先下跌20%到35%,形成圆弧底(杯部),随后反弹到前期高点附近,再小幅回落5%到15%形成柄部,柄部成交量萎缩,最终放量突破杯柄高点。
杯部深度通常不超过35%,柄部回调幅度不超过杯部深度的一半。柄部持续时间1到2周,长柄容易失败。突破点需要成交量比日均量放大40%以上。

用Python把形态翻译成数字
人眼识别杯柄容易,让计算机理解需要把形态拆解成可计算的指标。核心维度包括:
杯部深度:区间最高价与最低价之差除以最高价,落在0.2到0.35之间
杯部时长:最低点前后各20到60个交易日,整体杯部3到6个月
柄部回撤:柄部最低价与杯部最高价之差除以杯部深度,小于0.5
柄部缩量:柄部日均成交量小于杯部日均成交量的70%
突破确认:收盘价创杯部新高,且成交量大于20日均量的1.4倍
数据获取与清洗
使用tushare获取A股日线数据。免费版有频率限制,建议先下载全市场数据到本地数据库。
import tushare as ts
import pandas as pd
import numpy as np
pro = ts.pro_api('你的token')
df = pro.daily(ts_code='000001.SZ', start_date='20200101', end_date='20231231')
df = df.sort_values('trade_date').reset_index(drop=True)
df['trade_date'] = pd.to_datetime(df['trade_date'])
df.set_index('trade_date', inplace=True)
注意前复权处理。未复权数据在除权日会出现巨大跳空,破坏形态识别。tushare的daily接口返回不复权数据,需要配合adj_factor计算复权价。
形态识别函数实现
采用滑动窗口扫描。对每个交易日,向前回溯120个交易日作为潜在杯部区间。
def find_cup_handle(df, window=120, min_depth=0.2, max_depth=0.35):
results = []
for i in range(window, len(df)):
cup = df.iloc[i-window:i]
high_idx = cup['high'].idxmax()
low_idx = cup['low'].idxmin()
if low_idx <= cup.index[10] or low_idx >= cup.index[-10]:
continue
cup_high = cup.loc[high_idx, 'high']
cup_low = cup.loc[low_idx, 'low']
depth = (cup_high - cup_low) / cup_high
if not (min_depth <= depth <= max_depth):
continue
handle = cup.loc[high_idx:]
if len(handle) < 5 or len(handle) > 20:
continue
handle_low = handle['low'].min()
handle_retrace = (cup_high - handle_low) / (cup_high - cup_low)
if handle_retrace > 0.5:
continue
cup_vol = cup.loc[low_idx:high_idx, 'vol'].mean()
handle_vol = handle['vol'].mean()
if handle_vol > cup_vol * 0.7:
continue
today = df.iloc[i]
vol_ma20 = df['vol'].iloc[i-20:i].mean()
if today['close'] > cup_high and today['vol'] > vol_ma20 * 1.4:
results.append({
'date': today.name,
'cup_high': cup_high,
'cup_low': cup_low,
'depth': round(depth, 3),
'handle_retrace': round(handle_retrace, 3),
'breakout_vol_ratio': round(today['vol'] / vol_ma20, 2)
})
return pd.DataFrame(results)
回测验证与常见坑
识别出形态后,用未来20个交易日的收益率验证。欧奈尔原版要求突破后买入,跌破柄部低点止损。
def backtest(df, signals, hold_days=20):
trades = []
for _, row in signals.iterrows():
entry_idx = df.index.get_loc(row['date'])
if entry_idx + hold_days >= len(df):
continue
entry_price = df.iloc[entry_idx]['close']
exit_price = df.iloc[entry_idx + hold_days]['close']
ret = (exit_price - entry_price) / entry_price
trades.append(ret)
return np.mean(trades), np.std(trades), len(trades)
常见坑:
除权缺口未处理,导致假突破。必须使用前复权数据。
参数过拟合。杯部深度20%到35%是欧奈尔经验值,不同市场需要微调。A股波动大,可放宽到40%。
忽略大盘环境。CANSLIM强调只在上升趋势中买入。加入沪深300均线过滤,指数在200日均线下方时暂停开仓。
柄部时间太短。少于5个交易日的柄部往往是假柄,容易直接失败。
优化方向
用向量化替代循环。pandas的rolling配合argmax能大幅提速。计算杯部最高点位置时,用cup['high'].rolling(window).apply(np.argmax)。
引入机器学习过滤。把形态特征输入随机森林,用历史突破后收益作为标签,自动学习哪些杯柄更可能成功。特征包括杯部深度、柄部回撤、成交量萎缩比例、突破日相对大盘强度。
多周期共振。日线杯柄叠加周线MACD金叉,胜率提升明显。周线过滤掉日线级别的噪音。
基本面叠加。欧奈尔要求当季EPS增长25%以上。用tushare的fina_indicator接口获取净利润同比增速,只保留高增长股票。