4、因子择时信号构建:信号来源、合成方法与阈值设定
信号构建,说白了就是给因子装上一个“开关”。
没有信号的因子,就像没有瞄准镜的狙击枪——你扣动扳机,但不知道子弹会飞向哪里。我个人习惯把信号构建拆成三个环节:信号从哪来、怎么合成、阈值怎么定。今天咱们就一个一个聊透。
4.1 信号来源:三类核心数据源
我做过一个统计,在我经手的几十个因子择时项目中,信号来源基本逃不出这三类:宏观数据、技术指标、市场情绪。咱们分别看看。
4.1.1 宏观数据信号
宏观数据是“大气候”。比如PMI、CPI、社融数据、利率变化。这些数据发布频率低(月频或季频),但影响力巨大。
典型用法:
- PMI连续三个月回升 → 经济扩张期,利好周期因子
- 社融增速超预期 → 流动性宽松,利好小盘因子
- 十年期国债收益率上行 → 利空成长因子,利好价值因子
4.1.2 技术指标信号
技术指标是“小天气”。频率高、反应快,适合做短期择时信号。
我常用的几个:
- RSI(相对强弱指标):RSI > 70 超买,RSI < 30 超卖。但注意,强势行情中RSI可以长期在70以上,别急着反向做空。
- 布林带:价格触及上轨 → 短期过热;触及下轨 → 短期超跌。
- 成交量变化:量价背离是经典信号。价格创新高但成交量萎缩,说明上涨动能不足。
# 一个简单的RSI信号生成函数
def rsi_signal(price_series, period=14, overbought=70, oversold=30):
delta = price_series.diff()
gain = delta.where(delta > 0, 0).rolling(period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
# 生成信号:1=买入,-1=卖出,0=中性
signal = pd.Series(0, index=price_series.index)
signal[rsi < oversold] = 1
signal[rsi > overbought] = -1
return signal
4.1.3 市场情绪信号
市场情绪是“人心”。这东西最难量化,但往往最有效。
我常用的情绪指标:
- VIX指数:恐慌指数。VIX飙升时,市场避险情绪浓,高贝塔因子要减仓。
- 融资余额变化:散户杠杆情绪。融资余额快速增加 → 市场过热。
- 期权PCR(Put/Call Ratio):PCR > 1.2 极度悲观,PCR < 0.6 极度乐观。
- 百度搜索指数:搜索“牛市”、“抄底”等关键词的频率变化。
4.2 信号合成方法:把碎片拼成地图
单一信号源容易出错。我习惯把多个信号合成一个综合信号。常用的方法有三种。
4.2.1 等权合成法
最简单,也最稳健。每个信号源给相同的权重。
# 等权合成示例
signal_pmi = 0.8 # PMI信号,范围0-1
signal_rsi = 0.3 # RSI信号
signal_vix = 0.6 # VIX信号
composite_signal = (signal_pmi + signal_rsi + signal_vix) / 3
# 结果:0.567
优点:不会过度依赖某个信号。缺点:所有信号“一视同仁”,但实际中有些信号就是更准。
4.2.2 加权合成法
给历史表现好的信号更高权重。权重怎么定?我常用“滚动夏普比”或“IC值”来动态调整。
| 信号源 | 历史IC值 | 分配权重 |
|---|---|---|
| PMI信号 | 0.12 | 40% |
| RSI信号 | 0.08 | 27% |
| VIX信号 | 0.10 | 33% |
4.2.3 逻辑规则合成法
有时候,加权平均会“抹平”极端信号。比如三个信号分别是0.9、0.1、0.5,平均后变成0.5——但0.9那个信号其实非常强烈。这时候我更喜欢用逻辑规则。
# 逻辑规则合成示例
def logic_synthesis(pmi_signal, rsi_signal, vix_signal):
# 规则1:如果两个以上信号强烈看多,则看多
strong_bull = sum([pmi_signal > 0.7, rsi_signal > 0.7, vix_signal < 0.3]) >= 2
# 规则2:如果PMI和VIX同时看空,则看空
strong_bear = (pmi_signal < 0.3) and (vix_signal > 0.7)
if strong_bull:
return 1.0
elif strong_bear:
return -1.0
else:
return 0.0 # 中性
4.3 信号阈值设定:别让信号变成噪音
信号合成后,我们得到的是一个连续值(比如0到1之间)。但实际交易中,我们需要的是“开仓”或“不开仓”的离散决策。这就涉及到阈值设定。
4.3.1 固定阈值法
最简单:信号 > 0.7 开多,信号 < 0.3 开空,中间区域空仓。
但问题来了:0.7这个数字怎么来的?拍脑袋吗?
我建议用历史数据做回测,找到最优阈值。比如遍历0.5到0.9,看哪个阈值下的夏普比最高。
4.3.2 动态阈值法
市场波动率变化时,固定阈值会失效。比如2015年股灾期间,信号波动极大,0.7的阈值可能一天触发好几次。这时候我改用“滚动分位数”作为动态阈值。
# 动态阈值示例:使用过去60天的90%分位数
def dynamic_threshold(signal_series, lookback=60, quantile=0.9):
rolling_high = signal_series.rolling(lookback).quantile(quantile)
rolling_low = signal_series.rolling(lookback).quantile(1 - quantile)
# 生成信号
trade_signal = pd.Series(0, index=signal_series.index)
trade_signal[signal_series > rolling_high] = 1
trade_signal[signal_series < rolling_low] = -1
return trade_signal
4.3.3 多级阈值法
有时候,非黑即白的开仓/空仓太粗糙。我更喜欢用多级信号:
- 信号 > 0.8:强烈看多,满仓
- 信号 0.6-0.8:温和看多,半仓
- 信号 0.4-0.6:中性,空仓
- 信号 0.2-0.4:温和看空,半仓做空
- 信号 < 0.2:强烈看空,满仓做空
这样既保留了信号的强度信息,又避免了频繁交易。
4.4 知识体系总览
下面这张图,是我做因子择时信号构建时的核心框架。你可以把它当作一张“地图”,每次做信号设计时,对照着走一遍,基本不会漏掉关键环节。
你看,整个流程其实就三步:找信号 → 合成信号 → 定阈值。每一步都有坑,但每一步也都有成熟的解法。我个人觉得,信号构建最核心的不是技术,而是“对市场的理解”。你越理解市场在什么环境下会怎么走,你的信号就越有效。
好了,这一章就聊到这儿。记住:信号不是越多越好,而是越精越好。三个靠谱的信号,远胜于十个垃圾信号。