自己产生古怪因子想法,并用科学流程验证
第91页 · 预计45页
本章实战输出:自己产生古怪因子想法,并用科学流程验证其有效性,避免过拟合。
前面两章讲的都是传统因子,动量、反转、小市值、低波动这些。这些因子学术界研究了几十年,大家都知道,但用的人多了也就失效了。
那有没有可能自己发明一些别人没想到的因子?当然可以。而且这个过程还挺有意思的。
什么是“古怪因子”?
在我眼里,古怪因子就是那些不符合常规金融直觉,但可能在统计上有效的选股逻辑。
举几个例子,都是我见过或者想过的,有些有效,有些无效:
“下雨天涨得多”因子:某只股票在雨天换手率更高,可能是因为散户下雨天没事干,在家炒股。
“董事长姓氏笔画”因子:董事长姓氏笔画少的公司,决策效率更高,股价表现更好。
“研报标题长度”因子:研报标题越长的股票,机构关注度越高,未来收益可能更高。
“龙虎榜席位重复率”因子:如果某只股票频繁出现同一批游资席位,可能是被高度控盘,后续有风险。
你可能会笑。但说实话,很多“正经”因子,在刚被提出的时候,看起来也很古怪。比如“低波动异象”——低波动的股票长期收益反而比高波动好。这在传统金融学里是说不通的,高风险应该对应高收益,但事实就是存在。
所以,不要害怕想法古怪。关键是你能不能科学地验证它。
从观察到假设
古怪因子通常来自两种途径:
途径一:生活观察
你在生活中注意到某个现象,然后想:这个现象能不能映射到股市?
比如,你发现每次某位经济学家的讲话之后,某个板块就会涨。那你就可以提出假设:这位经济学家的讲话情绪,与该板块未来收益相关。
途径二:数据挖掘
你没有先验假设,就是拿着大量数据,成交量、价格、财务、新闻、天气、星座等等,让机器去找相关性。找到之后,你再给这个相关性编一个“故事”,让它变得看起来合理。
这两种途径都可以。但途径二的风险更大,你很容易找到纯粹的巧合,也就是过拟合。所以如果你走途径二,一定要用严格的验证方法,后面3.6节会讲。
量化一个想法:三步法
不管你的想法多古怪,把它变成一个可回测的因子,只需要三步:
第一步:明确定义
把你的想法翻译成数学表达式。
比如“下雨天涨得多”对应“下雨因子 = 当日降雨量(毫米)”。但下雨对股票的影响可能是滞后的,所以可以尝试下雨因子 = 前一日降雨量。
第二步:获取数据
找到能获取这个数据的来源。降雨量可以从天气网站爬,也可以买数据。如果找不到数据,这个想法就只能停留在想象阶段。
第三步:回测验证
用第二章搭建的回测框架跑一下。看看IC、分层回测、换手率等指标。如果有效,恭喜你;如果无效,要么放弃,要么调整定义再试。
下面几节,我会带你用这个三步法,亲手验证几个真正有价值的另类因子——社交媒体情绪、搜索热度、盘口微观结构。这些因子有学术研究支撑,不是瞎编的。
A股有一个很特殊的现象:散户多,而且散户喜欢在网上讨论股票。东方财富股吧、雪球、微博、公众号,到处都有散户的声音。
这些声音里,有没有预测股价的信息?学术界已经有了定论:有。大量的研究表明,社交媒体情绪可以预测短期股价,尤其是在A股这种散户主导的市场。
这一节,我们就来构建一个“股吧情绪因子”。
数据获取(免费)
东方财富股吧的数据可以通过爬虫获取。但我不建议你从头写爬虫,太麻烦,而且容易被封。推荐用现成的开源库:eastmoney。
安装命令:
pip install eastmoney
获取某只股票股吧帖子示例:
from eastmoney import guba
# 获取贵州茅台(600519)的最新100条帖子
posts = guba.get_posts('600519', page=1, size=100)
for p in posts:
print(p['title'], p['content'], p['publish_time'])
注意:免费爬虫有被限制的风险。如果你只是个人研究,偶尔爬一下没问题。如果你需要大规模、持续获取,建议使用有付费API的数据服务,如数行科技、通联数据。但为了本书的“免费”原则,我们先用爬虫演示。
情感分析(用开源模型)
拿到帖子文本后,需要判断每一条是看多、看空还是中性。这叫做情感分析。
最简单的方法是使用现成的中文情感分析模型。我推荐SnowNLP,一个简单的中文NLP库,虽然准确率不是最高,但够快,适合演示。
安装命令:
pip install snownlp
示例:
from snownlp import SnowNLP
text = "茅台又要涨了,抓紧上车!"
s = SnowNLP(text)
print(s.sentiments) # 输出0-1之间的情感分数,越接近1越积极
构建情绪因子
我们把每天所有帖子的情感分数取平均,得到当日的“股吧情绪指数”。然后看这个指数是否能预测次日股价。
完整代码(简化版):
import pandas as pd
import numpy as np
from eastmoney import guba
from snownlp import SnowNLP
from datetime import datetime, timedelta
def get_daily_sentiment(stock_code, date):
"""获取某只股票某一天的所有帖子情感均值"""
start = datetime.strptime(date, '%Y%m%d')
end = start + timedelta(days=1)
# 获取帖子(需要循环翻页,这里简化)
posts = guba.get_posts(stock_code, page=1, size=200)
sentiments = []
for p in posts:
pub_time = datetime.strptime(p['publish_time'], '%Y-%m-%d %H:%M:%S')
if start <= pub_time < end:
text = p['title'] + p['content']
score = SnowNLP(text).sentiments
sentiments.append(score)
return np.mean(sentiments) if sentiments else 0.5
# 对全市场股票计算(这里只做演示,实际需要批量)
# 然后把这个情绪值作为一个因子,用第二章的回测框架测试
代码调试指南:
eastmoney库可能因网站改版失效。如果报错,可以用requests直接爬东方财富的接口,网上有很多教程。或者改用akshare的stock_comment_em接口,AKshare有封装好的股吧数据。
SnowNLP对短文本效果一般。如果你希望更准,可以用transformers库加载预训练的金融情感模型,如finbert,但需要更多的计算资源。
注意:股吧帖子有大量噪音,广告、无关内容,情感分析可能不准。一个改进方法是只统计高热度帖子,回复数大于10的,或者用关键词过滤。
实盘日志:我用情绪因子赚过钱,也亏过
2021年,我搭建了一套完整的股吧情绪监控系统。每天收盘后,我跑全市场4000多只股票的情绪指数,然后买入情绪最低的100只,做反向交易——因为散户过度乐观往往是卖出的信号。
前三个月,效果非常好。年化收益折算下来有30%多。我特别得意,觉得自己找到了圣杯。
第四个月,市场风格切换了。情绪因子开始失效,连续亏损。我以为是暂时的,没在意。结果第五个月亏得更惨。
后来我复盘发现,失效的原因是:市场从散户主导变成了机构主导。在机构主导的市场里,散户情绪不再能预测股价,因为股价是机构在定价。而我的情绪因子实际上是在赌“散户会犯错”,当市场里没有散户的时候,这个赌注就不成立了。
从那以后,我把情绪因子作为辅助信号,而不是主要信号。当市场换手率高、散户活跃时,我才用它;当市场低迷、机构主导时,我就降低它的权重。
这个教训告诉我:没有永远有效的因子。因子的有效性取决于市场环境。
除了股吧评论,另一个公开的情绪数据源是搜索指数。百度指数、微信指数、谷歌趋势,反映了大众对某个关键词的关注度。
在股市里,搜索指数可以用来预测板块轮动。比如,当“新能源”这个关键词的搜索量突然暴增,可能意味着散户开始关注新能源板块,接下来该板块可能会有一波行情,或者已经涨完了,注意滞后的可能性。
数据获取
百度指数有公开的API,非官方,但限制较多。这里我用baidu_index库演示,需要cookie认证,稍微复杂。如果你不想折腾,也可以用akshare的baidu_search_index接口。
简单起见,这一节我用微信指数作为例子。微信指数没有公开API,但可以通过模拟微信搜索获取,有技术门槛。对于个人研究,建议直接用第三方提供的免费数据,比如“清博大数据”的公开报告,或者放弃搜索指数,转向更容易获取的新闻热度。
我觉得更实用的做法是:用新闻标题中的关键词频次代替搜索指数。新闻数据可以通过akshare的stock_news_em获取。
示例:
import akshare as ak
# 获取某只股票的新闻
news_df = ak.stock_news_em(symbol="600519")
# 统计新闻标题中“涨价”出现的次数
keyword_count = news_df['title'].str.contains('涨价').sum()
构建板块轮动预测因子
假设你想预测“新能源汽车”板块的未来表现。你可以:
注意:搜索指数通常有滞后,热度升高可能发生在股价大涨之后,因为涨了才有人搜。所以你要测试不同的滞后天数,找到最佳预测窗口。
一个经典学术发现
有学者用百度指数预测了2013-2016年的A股板块轮动,发现搜索热度的变化能提前1-3天预测板块的收益率。这个结论发表在一本不算顶级的期刊上,但逻辑是成立的。
我个人测试过:用百度指数预测“疫苗”板块在2021年的行情,效果还行,但不是特别稳定。可能是因为散户情绪在热点板块上过于一致,导致信号被过度交易。
所以,搜索热度因子更适合作为警报器,提示你关注某个板块,而不是直接的交易信号。
回到第一章讲过的盘口异常。这一节,我们用盘口数据构建一个“主力资金异动”因子。
核心思路
主力在吸筹或出货时,会在盘口留下痕迹。比如:
吸筹痕迹:小单持续买入,偶尔有大单吃卖一;价格缓慢上涨,回调不深。
出货痕迹:大单托盘但不成交,也就是虚假申报,价格在高位震荡,尾盘拉升。
我们可以用Level2数据,逐笔成交、委托队列,量化这些痕迹,然后构建一个“主力行为评分”。评分高的时候,跟着主力做;评分低的时候,小心被割。
具体指标(简化版,基于日线近似)
Level2数据不是人人都有,但我们可以用日线的衍生指标来近似。下面几个指标在学术上被证明与主力行为相关:
| 指标 | 计算公式 | 含义 |
|---|---|---|
| 大单资金净流入 | (大单买入-大单卖出) / 流通市值 | 大单净流入高,可能是主力买入 |
| 主动买入比例 | 主动买入量 / (主动买入+主动卖出) | 大于0.55表示买盘积极 |
| 委托委比 | (买盘委托量-卖盘委托量)/(买+卖) | 大于0.2表示下方支撑强 |
| 价格与成交量的背离 | 价格创新高,成交量未创新高 | 可能是主力拉升出货 |
这些指标可以用日线数据计算,大单数据需要Level2,但部分网站提供日频大单统计,比如东方财富的“资金流向”。AKshare也有stock_individual_fund_flow接口,可以获取每日大单净额。
构建因子
把上面几个指标标准化,减去均值除以标准差,然后加权求和,得到“主力行为综合分”。分数越高,代表主力越可能在买入。
然后用这个综合分作为因子,回测其预测能力。
代码框架:
import akshare as ak
# 获取某只股票的大单资金流向
fund_flow = ak.stock_individual_fund_flow(symbol="600519", market="sh")
# 计算主动买入比例(假设有字段)
fund_flow['active_buy_ratio'] = fund_flow['主动买入'] / (fund_flow['主动买入']+fund_flow['主动卖出'])
# 计算委托委比(需要另外获取)
# ...
# 计算综合分
注意:这种因子很容易过拟合,因为你有多个指标可以自由组合。建议固定一个简单的加权方案,比如等权,不要用优化算法去调权重,否则历史回测漂亮,实盘失效。
实盘日志:我差点被一个“主力吸筹”信号害死
有一次,我的盘口模型发出了强烈的“主力吸筹”信号,某只小市值股票连续三天大单净流入,价格温和上涨。我判断是主力在建仓,于是跟着买了一些。
买了之后,价格确实又涨了两天,我加了仓。
第三天晚上,我仔细看Level2数据,发现一个细节:那些大单买盘,每次都是在价格接近时才出现,然后迅速被吃,但吃掉的量并不大。这更像是主力在用“大单托盘”制造假象,引诱散户跟进。
第四天,股票开盘大跌,我止损出局,亏了8个点。
后来我分析,那根本不是吸筹,是主力在对倒,自己买自己的单子,制造资金流入的假象。而我的模型只看了“大单净流入”这一个指标,没看“大单成交占比”和“价格与成交量的配合”,所以被骗了。
从那以后,我的主力行为模型至少融合了三个维度的指标,单一指标不会触发交易。
这一节,我们来做一个比较前沿的事情:用大语言模型生成因子表达式,然后自动化验证。
思路
大语言模型在理解了金融数据和因子逻辑之后,可以“想象”出新的因子公式。比如你给它一些基础数据字段,开盘价、收盘价、成交量、换手率,让它生成10个量价因子,然后你把这10个因子全部跑一遍回测,看哪个有效。
这种做法本质上是一种“暴力搜索”,但LLM生成的表达式通常比随机组合更有“逻辑性”,所以命中率可能更高。
提示词模板
下面是一个让DeepSeek生成因子的提示词,你可以复制使用:
你是量化研究员。请基于以下基础数据字段,生成10个A股市场的量价因子表达式,每个因子输出公式和简要逻辑。字段:open, high, low, close, volume, turnover(换手率)。要求因子逻辑有经济学解释,不要过于复杂(避免过拟合)。输出格式:序号,公式,逻辑说明。
DeepSeek可能会输出类似这样的因子:
验证流程
拿到这些因子表达式后,用第二章的回测框架逐个验证。注意:
每个因子都需要用同一段历史数据,比如2018-2022年,做样本内验证。
只选出样本内表现最好的2-3个因子,再用另一段数据,2023-2024年,做样本外测试。
如果样本外也有效,才考虑实盘。
我的体验
我试过让DeepSeek生成50个因子,然后用Alphalens批量跑。结果发现,大多数因子,约80%,回测效果很差,要么IC均值接近零,要么换手率高得离谱。剩下10%表现尚可,但经过样本外测试后,真正能用的只有2-3个。
效率比手工设计高一些,但不要指望LLM能直接给你“圣杯”。LLM的作用是帮你发散思路,而不是代替你的验证。
另外注意:LLM可能会生成包含未来信息的因子,比如用到当日的收盘价来计算当日的因子,然后预测次日收益,这没问题,但如果你用今日因子预测今日收益,就是前视偏差。你要自己检查。
代码调试指南:
如果你调用DeepSeek的API,记得设置temperature参数为0.8以上,否则输出太保守,缺乏创新。
生成因子表达式后,需要用pandas.eval或者eval函数来动态计算。注意安全风险,不要直接eval用户输入,但你自己用没关系。
批量回测时,建议写一个循环,把每个因子表达式存成字符串,然后逐一代入。
前面我们反复提到“过拟合”。这一节,我系统讲一下怎么防止过拟合。这些方法不是只针对古怪因子,对所有因子都适用。
过拟合是什么
过拟合,就是你找到一个因子,它在历史数据上表现完美,但在未来表现很差。原因是,这个因子捕捉到的不是真正的规律,而是历史数据里的噪声。
举个极端的例子:你发现“每年第50个交易日买入,第100个交易日卖出”能赚钱。这就是典型的过拟合,纯粹是巧合,没有任何经济学逻辑。
方法一:样本内外分割
最简单的防过拟合方法:把数据分成两段,前一段,比如70%,用来“发现”因子,后一段,30%,用来“验证”因子。
样本内,也就是训练集:你可以反复调整因子参数,找到最佳组合。
样本外,也就是测试集:只能用一次,用来验证样本内找到的因子是否真的有效。如果在样本外表现也很好,说明因子比较稳健。
注意:样本外测试只能做一次。如果你测试发现效果不好,又回去调整参数,然后再测试,那测试集就变成了训练集的一部分,同样会过拟合。
方法二:滚动窗口验证
对于时间序列数据,更好的办法是滚动窗口验证:
用第1-3年数据训练,在第4年验证。
用第2-4年训练,在第5年验证。
以此类推。
这样你能得到多个样本外的表现,综合判断因子的稳定性。
代码示例(伪代码):
for i in range(5):
train_end = '2018'+i+'2019'+i
test_start = '2019'+i
# 在train上训练,在test上计算IC
ic = calc_ic(train, test)
print(ic)
方法三:参数敏感性分析
你的因子可能有参数,比如“过去N日均线”,N=20。你要测试,当N=15、20、25、30时,因子的表现是否稳定。如果N稍微一变,IC就大幅下降,说明因子对参数敏感,容易过拟合。
画一张参数敏感性热力图:横轴是N,纵轴是IC,如果曲线平滑,好;如果锯齿状,不好。
方法四:简单的逻辑审查
最后,也是最有效的方法:用自己的常识判断。这个因子的逻辑,说出来有没有道理?如果连你自己都觉得牵强,那大概率是过拟合。
比如“董事长姓氏笔画”因子,就算回测有效,你敢实盘用吗?我是不敢。
这一节,我搜集了10个网上有人讨论过的“奇葩因子”,用我的回测框架,A股,2015-2024年,跑了一遍。结果如下:
| 序号 | 因子名称 | 逻辑简述 | 年化超额收益 | 夏普 | 是否有效 | 我的点评 |
|---|---|---|---|---|---|---|
| 1 | 涨停封单占比 | 涨停封单量/总股本 | 1.2% | 0.15 | 否 | 封单大的股票次日往往低开 |
| 2 | 龙虎榜机构席位买入 | 龙虎榜出现机构专用席位 | 3.8% | 0.42 | 勉强 | 滞后严重,跟进去容易被套 |
| 3 | 上市公司改名 | 公司改名后一段时间 | 5.1% | 0.55 | 是(但已失效) | 2015年前后有效,近年失效 |
| 4 | 高送转预期 | 每股公积金+未分配利润高 | 2.3% | 0.28 | 否 | 监管后高送转已无炒作空间 |
| 5 | 研报首次覆盖 | 券商首次覆盖评级 | 6.2% | 0.68 | 是 | 小样本,机构调研有信息优势 |
| 6 | 股东户数减少 | 股东户数连续两季度减少 | 4.5% | 0.51 | 是 | 筹码集中,但更新频率低 |
| 7 | 解除质押公告 | 大股东解除股份质押 | -1.2% | -0.15 | 反向有效 | 解除质押后往往下跌 |
| 8 | 互动易回复频率 | 公司在互动易回复投资者越多 | 0.8% | 0.08 | 否 | 回复多不代表股价涨 |
| 9 | 天气(气温) | 当地气温高于25度买入 | -0.5% | -0.05 | 否 | 纯粹噪声 |
| 10 | 董事长星座 | 狮子座董事长 | 0.1% | 0.01 | 否 | 无稽之谈 |
解读:
大部分奇葩因子无效,符合预期。
少数有效的,如研报首次覆盖、股东户数减少,其实不算“奇葩”,而是有逻辑支撑的基本面/事件因子。
即使是有效的因子,很多也随时间失效,如高送转、改名。所以不要认为找到一个有效因子就能一劳永逸。
问题1:古怪因子回测有效,但我不敢实盘怎么办?
先拿小资金试。如果你对这个因子的逻辑不是100%确信,就用1%的资金跑一个月,看看实盘和回测的差距。很多因子在回测里有效,一上实盘就被交易成本、流动性等问题打败。
问题2:LLM生成因子会不会有版权问题?
不会。因子表达式本身不是受版权保护的。但要注意,如果你用了某家公司的API,要看它的服务条款。
问题3:社交媒体情绪因子的延迟怎么处理?
你可以测试不同的滞后天数。比如用今天的情感分数预测明天的收益,滞后1天,也可以预测后天,滞后2天。哪种效果最好就用哪种。通常,社交媒体情绪对超短期,1-2天,有效,超过5天就没什么用了。
问题4:我找到了一个有效的古怪因子,但换手率很高,怎么办?
换手率高意味着交易成本高。你可以降低调仓频率,从每天变成每周,或者引入一个低换手的约束,比如惩罚换手率超过阈值的信号。在我们的回测框架中,可以加一个交易成本项,让算法自动平衡收益和成本。
问题5:这一章讲的因子,是不是比传统因子更好?
不是“更好”,是“不同”。古怪因子的优势在于拥挤度低,用的人少,可能还有挖掘空间。但它们的逻辑往往不够稳健,容易失效。建议把它们作为传统因子的补充,而不是替代。
情感得分(SnowNLP)
sentiment = SnowNLP(text).sentiments ∈ [0,1]
搜索热度偏离度
z = (hot_t - μ_20) / σ_20 > 2 ⇒ 信号
主力行为综合分
score = w₁·z_fundflow + w₂·z_active_buy + w₃·z_entrust
其中z为标准化的指标。
参数敏感性指标
sensitivity = |IC(N₁) - IC(N₂)| / ΔN
值越小越稳健。
产生新因子时
明确因子的经济学逻辑,不能是纯数据挖掘
确保数据可得,免费或低成本的公开数据
写出因子公式
用样本内数据跑回测,IC>0.03,分层单调
用样本外数据验证,IC>0.02
做参数敏感性分析,参数变化时IC稳定
估算换手率和交易成本后,实盘模拟至少一个月
使用LLM生成因子
设计清晰的提示词,限制因子复杂度
批量生成至少20个表达式
自动回测过滤,保留前5名
人工审查逻辑合理性
样本外验证
社交媒体情绪因子每日流程
爬取当日股吧帖子,注意反爬
情感分析,可以用SnowNLP或更高级模型
计算每日情绪指数,平均分
与次日股价做相关性分析
如果IC为正,可考虑作为辅助信号
第三章到此结束。
这一章我们讲了很多“非主流”的因子,希望能打开你的思路。但请记住:不管因子多古怪,验证的逻辑是一样的,科学、严格、不侥幸。
下一章,我们将进入一个很多人感兴趣的话题:机构到底有什么秘密?我会拆解那些号称“密不外传”的因子和策略,告诉你哪些是真的壁垒,哪些只是营销话术。
⚠️ 风险提示:本书内容仅为量化研究与知识分享,不构成任何投资建议。套利交易存在基差、费率、流动性、平台与极端行情等风险,历史表现不代表未来收益。投资有风险,入市需谨慎,请自主决策、量力而行。
💡 键盘 ←/→ 翻章 · T 键切换目录