4、因子择时信号构建:信号来源、合成方法与阈值设定

信号构建,说白了就是给因子装上一个“开关”。

没有信号的因子,就像没有瞄准镜的狙击枪——你扣动扳机,但不知道子弹会飞向哪里。我个人习惯把信号构建拆成三个环节:信号从哪来、怎么合成、阈值怎么定。今天咱们就一个一个聊透。

4.1 信号来源:三类核心数据源

我做过一个统计,在我经手的几十个因子择时项目中,信号来源基本逃不出这三类:宏观数据、技术指标、市场情绪。咱们分别看看。

4.1.1 宏观数据信号

宏观数据是“大气候”。比如PMI、CPI、社融数据、利率变化。这些数据发布频率低(月频或季频),但影响力巨大。

典型用法:

  • PMI连续三个月回升 → 经济扩张期,利好周期因子
  • 社融增速超预期 → 流动性宽松,利好小盘因子
  • 十年期国债收益率上行 → 利空成长因子,利好价值因子
我的经验:宏观数据有个“滞后性”问题。数据发布时,市场可能已经price in了。我习惯用“预期差”——实际值 vs 市场预期值,这个差值往往比绝对值更有信号价值。

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 极度乐观。
  • 百度搜索指数:搜索“牛市”、“抄底”等关键词的频率变化。
避坑指南:我曾经用百度指数做情绪信号,结果发现“牛市”这个词的搜索量在2015年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
避坑指南:动态阈值有个陷阱——如果市场长期处于单边行情,滚动分位数会“漂移”。比如牛市里,90%分位数越来越高,导致你永远在追高。我遇到过这种情况,后来加了一个“绝对阈值”作为兜底:动态阈值不能超过0.9,也不能低于0.1。

4.3.3 多级阈值法

有时候,非黑即白的开仓/空仓太粗糙。我更喜欢用多级信号:

  • 信号 > 0.8:强烈看多,满仓
  • 信号 0.6-0.8:温和看多,半仓
  • 信号 0.4-0.6:中性,空仓
  • 信号 0.2-0.4:温和看空,半仓做空
  • 信号 < 0.2:强烈看空,满仓做空

这样既保留了信号的强度信息,又避免了频繁交易。

4.4 知识体系总览

下面这张图,是我做因子择时信号构建时的核心框架。你可以把它当作一张“地图”,每次做信号设计时,对照着走一遍,基本不会漏掉关键环节。

因子择时信号构建框架 信号来源 宏观数据 PMI、CPI、社融、利率 技术指标 RSI、布林带、成交量 市场情绪 VIX、PCR、融资余额 信号合成方法 等权合成 加权合成(IC动态调整) 逻辑规则合成 信号阈值设定 固定阈值 动态阈值(滚动分位数) 多级阈值

你看,整个流程其实就三步:找信号 → 合成信号 → 定阈值。每一步都有坑,但每一步也都有成熟的解法。我个人觉得,信号构建最核心的不是技术,而是“对市场的理解”。你越理解市场在什么环境下会怎么走,你的信号就越有效。

好了,这一章就聊到这儿。记住:信号不是越多越好,而是越精越好。三个靠谱的信号,远胜于十个垃圾信号。

专注资料整理