作者:老余捞鱼
原创不易,转载请标明出处及原作者。

写在前面的话:老余最近把欧奈尔的CANSLIM框架搬到了A股,又顺手写了一段Python代码。七个字母,七成看质地,三成看市场。今天我把这套流传了四十年的选股逻辑、国内适配方案和代码一并整理给你,看完你也许能少踩几个坑。
做量化这些年,我见过太多人把研究公司搞成了玄学。有人看消息面,有人看五行八卦,还有人把K线看成艺术品。说实话,这些方法不是完全没道理,但很难复制。
今天老余想和你聊一套很老派、但很实在的东西。上世纪八十年代,有个叫威廉·欧奈尔的投资人,干了件非常务实的事:他把那些年涨幅靠前的公司全部翻出来,逐个对比财报、股价和筹码结构,最后总结出七个字母。这套方法后来被他写进书里,英文名很直接,叫 CANSLIM。
它既不是纯粹的基本面分析,也不是纯粹的技术分析,而是把两者拧在了一起。核心思想只有一个:找到那些业绩在加速、价格在走强、市场也在配合的公司。
下面我把这七个字母拆开讲,再告诉你怎么在A股环境里用它,最后附上一段能直接跑的Python代码。
一、七个字母,到底在筛什么
CANSLIM是七个英文单词的首字母缩写。欧奈尔认为,一只标如果要走出持续行情,至少要满足这七条里的多数条件。
| 字母 | 英文原意 | A股里怎么看 |
|---|---|---|
| C | 当季每股收益增速 | 看扣非净利润同比增长,最好在25%以上 |
| A | 年度业绩增长 | 近三到五年复合增速稳定,ROE最好大于17% |
| N | 新产品、新管理、新高 | 新品类、新产业趋势、新管理层,或股价接近52周高点 |
| S | 供给与流通盘 | 流通市值适中,太小波动剧烈,太大拉不动 |
| L | 领涨还是跟涨 | RPS相对强度指标,最好大于80 |
| I | 机构关注度 | 公募、北向资金持仓,且季度环比有新增 |
| M | 市场大势 | 沪深300或上证指数站稳200日均线上方 |
C 和 A:先看过往业绩
欧奈尔对业绩的要求很苛刻。一个季度好不算好,他要看最近这个季度的扣非净利润增速比去年同期高多少,而且门槛是25%。不是5%,也不是10%,是25%。
这个标准直接把大部分平庸公司挡在了门外。你想,一家公司如果当季利润能加速增长,说明它的产品或服务正在被市场更快地接受。A股里看这一条,建议直接拉财务报表里的”扣非净利润同比增长”,避免被一次性非经常性损益干扰。
A则是看持续性。一年好可能是运气,连续三年好才是能力。欧奈尔喜欢那些年均利润增速稳定在25%以上,同时净资产收益率ROE超过17%的公司。这两个数字不神圣,但确实能把很多周期股和概念股过滤掉。
N:有没有新故事
欧奈尔发现,几乎所有的大行情背后都有一个”新”字。新产品、新技术、新管理层、新产业趋势,都算。没有新催化,资金很难形成共识。
他还有一句很反常识的话:要买那些创52周新高的公司。很多人觉得涨了那么多还能买吗?欧奈尔的数据结论是可以,而且很多时候是新行情的开始,不是结束。当然,这里要看的是接近新高,而不是已经翻了五倍的那种高位。
S:流通盘别太大
这个指标在美股和A股的理解略有不同。欧奈尔原话是看流通股数量,最好在2500万股以下,这样买盘进来更容易推动价格。A股很多公司股本都偏大,所以不能机械照搬。老余建议换成看流通市值,回避那些几万亿的巨无霸,重点关注500亿以下的中小盘,筹码结构更轻。
L:RPS相对强度
这是欧奈尔体系里我最看重的一条,也是国内量化圈这几年用得最多的一个指标。RPS全称Relative Price Strength,股价相对强度。
计算方法很简单:把全市场所有公司过去250天的涨幅排个名,如果一家公司排在前20%,它的RPS就是80以上。欧奈尔只关注RPS大于80的公司。他的逻辑是:既然要买,为什么不买同一行业里最强势的那个?
国内有些量化团队用120日或60日RPS做辅助,但250日依然是主流基准。这个指标的好处是客观,不掺杂主观感情。
I:机构是不是在加仓
欧奈尔认为,一家要走出大趋势的公司,背后必须有机构在持续买。但这里有个微妙的平衡:机构要有,但不能太多。如果全市场的基金都重仓了,后续谁还能继续买?
在A股里看这条,可以跟踪公募季报持仓变化和北向资金持股占比。环比出现新增机构,或者北向在稳步流入,都是积极信号。
M:大势环境
这一条最容易被忽略,也最重要。欧奈尔说,如果整个市场处于明确的下行趋势,再好的公司也不要轻易碰。因为统计上看,大约四分之三的公司会跟着大势走。
A股里就是看沪深300指数是否在200日均线上方。如果指数在均线下方运行,说明大势偏弱,这时候再好的筛选结果也要打个折扣。
二、这套逻辑在A股能跑通吗
直接说结论:能跑通,但要改改用。
雪球和东方财富上很早就有研究者把CANSLIM本土化。有人照着这个思路做了一个”胜券50“组合,长期表现显著跑赢了沪深300指数。国内也有量化团队把RPS指标集成进选股系统,用来识别短期强势股。
不过A股和美股有三个明显差异,你得知道:
- 财务数据披露节奏不同。A股季报披露有固定窗口,而且经常踩点发布,数据滞后性比美股更严重。
- 流通盘和筹码结构差异大。A股小市值公司波动极大,RPS高的时候可能已经在高位;而大盘蓝筹RPS很难冲到90以上。所以S和L要联动看。
- 机构行为更集中。A股的公募和北向资金偏好很明确,容易在个别板块形成拥挤。I这条要关注新增机构,而不是总持仓。
所以老余的建议是:把CANSLIM当作一个漏斗,而不是一个开关。它的作用是帮你从四千多家公司里快速筛出一个几十家的观察池,后面的工作还得靠你自己。
三、自己动手:A股版CANSLIM筛选器
下面这段代码用到了 akshare,这是国内量化爱好者最常用的免费数据接口之一,不需要注册token,安装就能用。
环境准备:在终端执行
pip install akshare pandas numpy即可。
代码逻辑分为四步:获取A股列表、批量计算技术指标和RPS、接入财务数据(你也可替换成Tushare Pro接口)、综合打分输出CSV。
import akshare as ak
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def get_stock_pool():
"""获取A股池,剔除ST、退市股和北交所"""
df = ak.stock_zh_a_spot_em()
# 过滤ST、退市、名称异常
df = df[~df['名称'].str.contains('ST|退', regex=False, na=False)]
# 北交所8开头,科创板688开头(可选过滤)
df = df[~df['代码'].str.startswith('8')]
df = df[~df['代码'].str.startswith('68')]
return df[['代码', '名称', '总市值', '换手率']].copy()
def fetch_hist_and_compute(code):
"""获取历史数据,计算250日涨幅与200日均线"""
try:
start = (datetime.now() - timedelta(days=380)).strftime("%Y%m%d")
end = datetime.now().strftime("%Y%m%d")
hist = ak.stock_zh_a_hist(
symbol=code, period="daily",
start_date=start, end_date=end, adjust="qfq"
)
if len(hist) < 250:
return None
close = hist['收盘']
ret_250 = (close.iloc[-1] - close.iloc[-250]) / close.iloc[-250]
ma200 = close.rolling(200).mean().iloc[-1]
high_52w = close.tail(250).max()
return {
'return_250': ret_250,
'price': close.iloc[-1],
'ma200': ma200,
'high_52w': high_52w
}
except Exception:
return None
def get_financial_proxy(code):
"""
财务数据建议通过 Tushare Pro / AKShare 财务接口补充。
此处为演示占位;实际运行时应接入季度净利润增速与ROE。
"""
# 示例接口参考:
# ak.stock_profit_sheet_by_quarterly_em()
# 或 tushare pro 的 fina_indicator 接口
return {'eps_growth': None, 'roe': None}
def canslim_a_share_screener(top_n=120):
print(">>> 正在加载A股实时数据...")
pool = get_stock_pool().nlargest(top_n, '总市值')
# 第一步:批量获取技术指标
tech_list = []
for _, row in pool.iterrows():
info = fetch_hist_and_compute(row['代码'])
if info:
info['代码'] = row['代码']
info['名称'] = row['名称']
info['总市值'] = row['总市值']
info['换手率'] = row['换手率']
tech_list.append(info)
if not tech_list:
print("未获取到有效数据,请检查网络或数据源。")
return pd.DataFrame()
tech_df = pd.DataFrame(tech_list)
# 第二步:计算RPS(250日涨幅的市场百分位)
tech_df['rps'] = tech_df['return_250'].rank(pct=True) * 100
# 第三步:逐条打分
results = []
for _, row in tech_df.iterrows():
# C: 当季净利润增速 ≥ 25%(需接入财务数据)
c = False # 示例:row['eps_growth'] >= 0.25
# A: 年度业绩增长 + ROE(需接入财务数据)
a = False # 示例:row['roe'] >= 0.17
# N: 股价距离52周高点在15%以内
n = (row['price'] / row['high_52w'] >= 0.85) if row['high_52w'] else False
# S: 流通供给偏好中等偏小(示例:总市值低于500亿)
s = row['总市值'] < 500_000_000_000
# L: RPS ≥ 80(领涨股)
l = row['rps'] >= 80
# I: 关注度适中(用换手率粗略模拟流动性,非真实机构占比)
i = 0.02 <= row['换手率'] <= 0.15
# M: 价格在200日均线上方(大势向上)
m = row['price'] > row['ma200'] if pd.notna(row['ma200']) else False
score = sum([c, a, n, s, l, i, m])
results.append({
'代码': row['代码'],
'名称': row['名称'],
'得分': score,
'RPS': round(row['rps'], 1),
'距52周高点': f"{row['price']/row['high_52w']:.1%}" if row['high_52w'] else '-',
'总市值(亿)': round(row['总市值']/1e8, 1),
'N_近新高': '是' if n else '否',
'S_中小盘': '是' if s else '否',
'L_RPS≥80': '是' if l else '否',
'I_换手适中': '是' if i else '否',
'M_均线上方': '是' if m else '否',
})
df = pd.DataFrame(results).sort_values('得分', ascending=False)
df.to_csv("canslim_a_share.csv", index=False, encoding='utf-8-sig')
print(f">>> 扫描完成,结果已保存,共 {len(df)} 条。")
return df
if __name__ == '__main__':
df = canslim_a_share_screener(top_n=100)
print(df.head(10)[['代码','名称','得分','RPS']].to_string(index=False))关于财务数据的说明:上面代码里C和A打了占位符,因为akshare的实时行情接口不直接返回季度EPS增速和ROE。建议有需求的读者接入 Tushare Pro 的fina_indicator 接口,或akshare的财务报表接口,把 eps_growth 和 roe 补上,整个框架就跑通了。
运行后会在当前目录生成一个 canslim_a_share.csv,按得分从高到低排列。你可以把 top_n 调大去扫更多公司,但注意全市场4000多只跑下来需要一定时间,建议晚上挂机跑。
四、跑出来之后,怎么用这个结果
代码只是帮你缩小范围。假设一家公司得了6分或7分,说明它同时满足多个条件,进入了”观察池”。但这不意味着你可以闭眼进场。
老余的习惯是:打分高的公司,我会再花十分钟看三件事。
- 看行业。它所处的行业是不是当前有产业催化的方向?这条需要验证。
- 看图形。是不是处于平台突破或杯柄形态?欧奈尔对K线形态有一套自己的叫法,核心就是不要在深度调整中接飞刀。
- 看筹码。近期有没有大额解禁?股东有没有减持公告?S和I不只是数字,还要结合具体事件。
如果这三件事都过关,再谈下一步。记住,CANSLIM是漏斗,不是触发器。
五、几个要避开的坑
任何方法都有边界,老余必须如实告诉你。
第一,它天生偏向成长风格。如果你信奉低估值逆向投资,这套方法会让你很不舒服。它筛出来的都是已经涨过一段、看起来”不便宜”的公司。
第二,数据滞后是硬伤。财报有披露期,等你看到季度增长25%时,可能已经过去两个月。欧奈尔当年用的是即时的机构数据,我们现在用免费接口,延迟是不可避免的。
第三,A股的小市值陷阱。S这条如果理解偏了,容易筛到一堆庄股或流动性极差的冷门公司。建议把总市值下限也设好,比如不低于50亿,避开仙股。
第四,RPS高不代表后续一定强。RPS是后视镜,反映的是过去250天谁涨得好。如果一家公司全靠一个消息刺激涨到前5%,RPS会很高,但基本面可能撑不住。所以要配合C和A一起看。
老余的一句话总结:CANSLIM的核心价值,是逼你在”好公司”和”好走势”之间找一个交集。很多人只看其中一个,欧奈尔要求你同时满足。这个思维习惯本身,就比任何单一指标都值钱。
六、写在最后
写这篇文章的时候,我刻意避开了那些让你”热血沸腾”的词汇。因为做投资,第一步是承认自己没有水晶球,第二步是建立一套可以重复执行的规则。
CANSLIM流传了四十年,不是因为它是完美的系统,而是因为它把”找强势股”这件复杂的事,拆成了七个可以量化、可以检验的步骤。在A股,数据源要换,参数要调,但底层逻辑没变:业绩加速、价格走强、市场配合。
希望这段代码和这套思路,能帮你在四千多家公司里,更快地找到值得花时间去研究的少数派。
风险提示:本文仅供参考,不构成投资建议。投资有风险,入市需谨慎。
版权声明:本文为原创内容,转载请注明出处。
#量化选股 #CANSLIM #欧奈尔 #A股 #Python量化 #相对强度RPS #成长股筛选 #财务指标 #机构持仓 #市场趋势
Be First to Comment