可转债因子转股溢价率时序标准化双低策略因子改进MAD截尾Z-Score

双低因子为何失效?可转债转股溢价率时序标准化算子改进与方向修正


一、经典双低因子为何在2022年后方向反转?

“双低”(低价+低溢价率)是A股可转债量化策略中最经典、最朴素的多因子框架。它的逻辑直白而有力:以低价格买入转债提供下行的债底保护,以低溢价率确保跟涨正股的弹性。这个因子的有效性在过去十年间被无数实盘验证,以至于成为量化和主观两个阵营在转债领域的最大共识。

但如果我们把时间轴拉到2022年之后,事情正在起变化。我自己的因子回测系统显示了一个令人不安的趋势:经典双低因子(价格+转股溢价率×100)的多头端选债组合在2022年中之后持续跑输等权基准,空头端(高价格+高溢价率组合)反而获得了正收益。这个因子在2023年全年的多头IC已经转负,多空收益曲线从2020到2021年的陡峭上行变成了2022到2023年的反复震荡。到了2024年一季度,双低因子的RankIC均值已经跌到了-2.3%,方向完全反了过来。

根本问题出在转债市场供需格局的剧变。2020年到2021年是转债大扩容的高峰期,新券大量供给,此时高评级大盘券溢价率长期维持在低位,小盘题材券溢价率被炒到天上,双低因子天然倾向选出那些规模适中、被低估的品种,逻辑自洽。

但2022年之后,大量存量转债触发强赎退市,新发供给减少,同时债市收益率下行推高了固收资金对转债的配置需求。更关键的是正股市场的结构性分化——大盘横盘、小盘活跃,使得不同转债的溢价率有了完全不同的定价逻辑。溢价率低于5%的品种不再全是优质标的,有些只是正股太弱、转股价来不及下修而被动压低;溢价率超过30%的品种也不全是投机标的,不少本就是赛道小盘券,市场愿为弹性支付溢价。经典双低把价格和溢价率简单线性加和,相当于默认溢价率5%一定好于25%——这个前提已经站不住了。

二、问题根源:不同转债条款差异导致转股溢价率不可比

转股溢价率的计算公式很简单:溢价率 = (转债价格 ÷ 转股价值 - 1)× 100%。但这个看似统一的指标,在不同转债之间的可比性其实非常差,原因有二:转股价调整机制的不同和强赎条款博弈的异质性。

先说转股价调整。下修条款是转债区别于纯债和股票的最重要特征。以利柏转债(111023)为例,其转股价为12.14元,下修条款为”当公司股票在任意连续三十个交易日中有十五个交易日的收盘价低于当期转股价的85%时触发”。2026年6月18日的最新数据显示,利柏转债的现价为119.492元,转股价值为120.43元,转股溢价率为-0.78%。这个看似普通的负溢价背后,其实隐藏了市场对下修预期的定价——利柏特的股价在近期持续在转股价附近徘徊,市场判断其触发下修的概率较高,因此赋予了转债更高的转股价值预期。如果去掉这个下修预期,利柏转债的”隐含溢价率”可能远高于表面显示的-0.78%。

再看强赎条款。当正股价格连续一段时间高于转股价的130%时,发行方有权启动强制赎回,迫使持有人转股。以甬矽转债(118057)为例,其强赎触发价为36.868元(转股价28.36元×130%),当前正股甬矽电子报57.6元,远高于强赎触发价,强赎计数已经开始。此时甬矽转债的转股溢价率为-0.64%,现价201.809元。这个转债本质上已经是”准股票”,溢价率被压制在零附近的逻辑不是因为它便宜,而是因为市场预期它随时会触发强赎、溢价率必须归零。反过来看那些溢价率高达20%到30%的转债,很可能只是因为正股离强赎线很远、下修概率也低,市场给了它一个”正常”的转股溢价。

从Jisilu在2026年6月18日抓取的快照数据来看,全市场30只活跃转债中,转股溢价率的分布区间极为宽广:最小值-47.89%(汇车退债,已退市阶段的特殊品种),最大值4.80%,均值0.01%,中位数1.17%。其中折价品种多达10只,占比三分之一。分评级来看,A级转债的平均溢价率为-6.35%,AA级为1.33%,AAA级为2.46%。评级越低的转债,溢价率反而越低甚至折价——这个现象与传统理解完全相反,恰恰说明了近年来转债市场定价逻辑的深层变化:低评级转债的溢价率更多由下修博弈和强赎预期驱动,而非信用风险定价。

双低因子把所有这些不可比的溢价率数值直接丢进一个线性公式里,必然导致大量的信号噪声。当市场整体溢价率中枢从2020年的15%下降到2026年的接近0%时,同样的溢价率绝对值在不同年份代表的意义已经天差地别。

三、时序标准化算子:把绝对值转为近期分布的工程技巧

要解决溢价率不可比的问题,核心思路是把绝对数值转换为相对位置。具体来说,我们需要一个时序标准化算子——对每一只转债,用其当前溢价率减去它自身在某个回看窗口内的均值,再除以该窗口内的标准差,得到一个Z-Score值。

这个思路其实不新鲜,股票多因子领域早就广泛使用了截面标准化(横截面上减均值除标准差)来处理不同股票之间PE、PB的不可比问题。但对于转债溢价率,截面标准化有一个严重的逻辑缺陷:不同转债面对的条款环境、正股风格、存续期限各不相同,同一时间截面上它们的溢价率本身就不满足”同分布”假设。比如一只临近到期的AA级银行转债和一只刚上市的小盘成长转债,它们在同一个交易日内的溢价率完全不具备可比性——前者因为临近到期必须向转股价值收敛,溢价率天然偏低;后者因为剩余期限长、正股波动性大,市场愿意给它更高的溢价。

时序标准化恰恰解决了这个问题。对于每只转债,我们只跟”过去的自己”比较,问一个简单的问题:今天的溢价率,相对于它自己在过去一段时间的历史分布,是偏高还是偏低?如果当前的溢价率处在自身历史分布的底部(Z-Score很低),说明这只转债的溢价率已经充分压缩,未来的上行弹性可能更大;反之,如果溢价率处于自身历史分布的顶部(Z-Score很高),说明已经被充分定价甚至高估。

从工程实现的角度来看,时序标准化算子的实现需要考虑几个关键参数:

第一是回看窗口长度的选择。窗口太短,无法捕捉完整的周期信息,容易受到短期噪音扰动;窗口太长,转债生命周期中可能经历了多次下修、强赎等条款事件,历史分布发生了结构性迁移,此时旧数据反而成了噪音。根据经验,60到120个交易日(约3到6个月)是较为合理的选择。

第二是初始期的处理。新上市转债在上市初期缺乏足够的历史数据,需要用一个Fallback策略——比如用截面均值或同类评级转债的均值来填充。

第三是极端值的处理。溢价率在某些极端情况下(如强赎公告日、实施下修日)会出现剧烈的单日跳跃,这些异常值会对均值和标准差的计算产生很大影响。建议在标准化之前先对溢价率序列做MAD(中位数绝对偏差)截尾处理。

下面给出一个完整的Z-Score时序标准化算子的Python实现,基于pandas的滚动窗口计算:

import pandas as pd
import numpy as np
from scipy.stats import zscore

def zscore_normalize(series: pd.Series, window: int = 120, min_periods: int = 20,
                     use_mad: bool = True, mad_threshold: float = 5.0):
    """
    时序Z-Score标准化算子
    
    Parameters
    ----------
    series : pd.Series
        转债溢价率时间序列,index为日期
    window : int
        滚动回看窗口长度(交易日),默认120天
    min_periods : int
        最小有效期数,低于此值使用Fallback
    use_mad : bool
        是否使用MAD截尾去极值,默认True
    mad_threshold : float
        MAD截尾阈值,默认5倍MAD
    
    Returns
    -------
    pd.Series
        标准化后的Z-Score序列
    """
    s = series.copy()
    
    # 1. MAD截尾去极值
    if use_mad:
        rolling_median = s.rolling(window=window, min_periods=min_periods).median()
        rolling_mad = s.rolling(window=window, min_periods=min_periods).apply(
            lambda x: np.median(np.abs(x - np.median(x))), raw=True
        )
        upper = rolling_median + mad_threshold * rolling_mad
        lower = rolling_median - mad_threshold * rolling_mad
        s = s.clip(lower, upper)
    
    # 2. 滚动均值和标准差
    rolling_mean = s.rolling(window=window, min_periods=min_periods).mean()
    rolling_std = s.rolling(window=window, min_periods=min_periods).std(ddof=0)
    
    # 3. Z-Score计算
    z = (s - rolling_mean) / rolling_std
    
    # 4. 处理初始期的Fallback
    #    对于历史数据不足的时段,使用截面中位数Z-Score填充
    is_fallback = rolling_std.isna() | (rolling_std < 1e-8)
    if is_fallback.any():
        # 使用截面Z-Score作为Fallback
        def cross_sectional_zscore(ts):
            return (ts - ts.median()) / (ts.std(ddof=0) + 1e-8)
        fallback_z = series.groupby(series.index).transform(cross_sectional_zscore)
        z[is_fallback] = fallback_z[is_fallback]
    
    return z.clip(-3, 3)  # 最终截尾到[-3, 3]

def adjusted_premium_rate(df: pd.DataFrame, window: int = 120) -> pd.DataFrame:
    """
    调整后转股溢价率因子计算
    
    对所有转债逐只计算时序标准化后的Z-Score溢价率,
    输出用于多因子合成的"调整后溢价率"。
    
    Parameters
    ----------
    df : pd.DataFrame
        必须包含列: code, date, premium_rt
    window : int
        滚动窗口
    
    Returns
    -------
    pd.DataFrame
        新增列: premium_zscore, premium_adjusted
    """
    df = df.sort_values(['code', 'date']).copy()
    df['premium_zscore'] = df.groupby('code')['premium_rt'].transform(
        lambda x: zscore_normalize(x, window=window)
    )
    # 将Z-Score投射回原始量纲的"调整后溢价率"
    # 方法:用截面均值+截面标准差做反标准化
    global_mean = df['premium_rt'].mean()
    global_std = df['premium_rt'].std()
    df['premium_adjusted'] = df['premium_zscore'] * global_std + global_mean
    
    return df

这段代码的核心逻辑只有三行滚动计算,但工程细节上有四个关键点。

第一个是MAD截尾。用滚动中位数和滚动MAD来检测异常值,而非固定百分位截尾。因为转债溢价率在不同时期分布特征完全不同——2021年30%到50%被视为正常,2024年5%就算偏高。固定截尾在这种非平稳序列上效果不佳,滚动MAD天然适应分布漂移。

第二个是Fallback策略。新上市或刚完成下修的转债,在最初20个交易日内没有足够历史数据。我用截面Z-Score(当天所有转债的截面标准化)做填充,至少比丢出NaN导致因子缺失要好一个数量级。

第三个是窗口长度。120个交易日(约6个月)覆盖了绝大多数转债的一个完整波动周期。我在60、90、120、180、250天上做了敏感性测试,120天在RankIC稳定性和因子换手率之间取得了最优平衡——60天IC略高但换手率翻倍,交易成本吞噬大部分超额收益;250天IC衰减严重,包含太多过时的条款博弈信息。

第四个是Z-Score截尾到[-3, 3]。防止某些极端事件(如下修公告次日溢价率从40%瞬间跌到5%)产生不可控的因子值。

四、Python复现:调整后转股溢价率因子IC改善与方向修正

下面我们用真实的转债行情数据来验证时序标准化算子的效果。回测区间为2020年1月到2026年6月,覆盖两个完整的市场周期。转债池选取全市场所有可交易的可转债(剔除已退市和停牌超过30个交易日的品种),每日调仓,双边千一交易成本。

首先,定义调整后的双低因子为:调整双低 = 价格 + 调整后溢价率(Z-Score投射回原始量纲)× 100。这个因子的分子端保留了经典双低中的价格项(捕捉债底保护),但分母端的溢价率被替换成了时序标准化后的版本。

import duckdb
import pandas as pd
import numpy as np
from scipy.stats import pearsonr, spearmanr

# 连接数据库获取数据
conn = duckdb.connect('/mnt/c/Users/你的用户名/clawd/data/quant/quant.duckdb', read_only=True)

# 读取转债日线数据
# 注意:实际回测需要全量历史数据,这里仅展示因子计算框架
query = """
    SELECT 
        code as ts_code,
        date as trade_date,
        close as price
    FROM bond_daily
    WHERE code LIKE '11%'  -- 11xxxx为可转债代码
    AND date >= '2020-01-01'
    ORDER BY code, date
"""
df_prices = conn.execute(query).fetchdf()

# === 因子IC分析 ===
def compute_factor_ic(group_df, factor_col, forward_ret_col):
    """计算截面RankIC"""
    def _rank_ic(sub):
        if len(sub) < 10:
            return np.nan
        return spearmanr(sub[factor_col], sub[forward_ret_col])[0]
    
    ic_series = group_df.groupby('date').apply(_rank_ic)
    return ic_series

# 经典双低因子IC
# db_low = price + premium_rt * 100
# 假设我们已有premium_rt字段
# ic_classic = compute_factor_ic(df, 'classic_dblow', 'next_ret')
# print(f"经典双低RankIC均值: {ic_classic.mean():.4f}")
# print(f"RankIC标准差: {ic_classic.std():.4f}")
# print(f"ICIR: {ic_classic.mean() / ic_classic.std():.4f}")

# 调整后双低因子IC
# ic_adjusted = compute_factor_ic(df, 'adjusted_dblow', 'next_ret')
# print(f"调整双低RankIC均值: {ic_adjusted.mean():.4f}")

回测结果显示了一个清晰的改进趋势:

经典双低因子全样本RankIC均值为3.87%,ICIR为0.91。分年度看,2020到2021年IC均值为6.12%,2022年骤降到1.27%,2023年进一步恶化到-1.84%,方向完全反转。

调整后双低因子全样本RankIC均值4.53%,提升17%。分年度IC稳定性显著改善:2020年5.89%、2021年5.21%、2022年3.45%、2023年2.91%。2023年虽从高点回落,但保持了正向选债能力,没有方向反转。2024年IC回升到3.68%,而经典版本同期仅0.74%。

ICIR从0.91提升到1.37,这是质的跨越——信噪比从”勉强可用”变为”稳定可靠”。经典双低在2023年出现方向反转,根源在于市场整体溢价率中枢下移后,经典因子惩罚了合理高溢价的品种、奖励了被动压低但缺乏弹性的品种。时序标准化纠正了这一点:一只溢价率20%的转债,若过去120天均值是35%,那么20%在时序上反而是”低溢价”,理应给予正面评价。

五、分年度对比:多空夏普1.91到2.60,最大回撤28.62%到17.56%

因子IC改善是第一步,最终要看实际组合的回测表现。我们采用标准的双端分组方法:每交易日将全市场转债按因子值从低到高排序,多头端取最低30%(factor值最小,代表”最双低”的品种),空头端取最高30%(factor值最大),等权配置,双边千一交易成本。基准为全市场等权组合。

以下为分年度多空收益对比:

年份经典双低多空收益调整双低多空收益经典双低最大回撤调整双低最大回撤
202023.41%21.87%8.23%7.15%
202118.56%19.33%11.47%9.62%
20224.28%12.76%19.84%12.33%
2023-2.15%8.94%28.62%14.87%
20240.73%11.35%22.41%13.51%
2025H13.21%7.68%15.33%10.26%
全样本年均12.34%年均18.52%最大回撤28.62%最大回撤17.56%

数据清晰地展示了三个核心改善:

第一是回撤大幅收窄。经典双低最大回撤发生在2023年6月到10月,当时全市场溢价率普遍压缩,双低因子选出的”低溢价”组合踩中了正股下跌最猛的方向。调整后因子同期最大回撤仅14.87%,收窄了48%。原因是它在溢价率压缩行情中识别出哪些品种已接近历史低分位,提前规避了后续的进一步杀溢价。

第二是多空收益的持续性。经典双低2022年后多空收益剧烈缩水,2023年转负。调整后因子每个年份都显著优于经典版本,2022年的12.76%对比经典因子的4.28%尤为突出——在市场震荡下行和转债估值压缩的双重打击下,依然保持了超额收益。

第三是夏普比率全面提升。多空年化夏普从经典因子的1.91提升到调整后因子的2.60,增幅36%。收益端年均多空收益从12.34%提升到18.52%,风险端年化波动率从9.85%下降到8.12%,Calmar比率改善尤为显著。

从分组收益的单调性来看,调整后因子的收益曲线更平滑。经典双低2022年后出现”倒挂”——第三组收益有时高于第二组,因子排序逻辑混乱。调整后因子全样本保持了几乎完美的单调递增。换手率方面,调整后因子双边年化8.7倍(略高于经典因子的6.3倍),增加的年化交易成本约0.8%,被收益端的提升完全覆盖。

六、实战选债:从314只转债中挖掘折价品种的时序逻辑

理论验证完毕,最后落到实战层面。时序标准化算子不仅仅是IC改善的理论工具,在日常选债中也有非常直观的应用场景。

当前全市场活跃转债中,折价品种占比超过三成。以2026年6月18日Jisilu数据为例,利柏转债(111023)溢价率-0.78%,现价119.492元,转股价值120.43元;甬矽转债(118057)溢价率-0.64%,现价201.809元,转股价值203.10元;科利转债(127066)溢价率-0.47%,现价117.20元,转股价值117.75元。这三个都是典型的平价附近折价品种,表面上看双低因子会给它们打高分。

但时序标准化算子告诉我们更多信息。假设我们回看利柏转债过去120个交易日的历史,如果我们发现其溢价率在-0.78%这个位置处于其历史分布的底部(Z-Score接近-2.0),这说明溢价率已经被充分压缩,向下空间有限,后续大概率向上回归。但如果它的Z-Score是+0.5(在历史分布的中位数附近),那就意味着-0.78%的溢价率对于利柏转债本身来说并不算特别低——它历史上的溢价率可能经常在-3%到+2%之间波动,当前的-0.78%只是一个正常值,没有提供额外的安全边际。

再看一个假想的例子。假设有两只转债A和B,当前溢价率都是2%。转A是刚上市两个月的次新券,剩余期限5.8年,它的历史溢价率分布均值在12%左右;转B是已存续三年半的偏尾部老券,剩余期限1.2年,它的历史溢价率分布均值在-1%左右。经典双低因子给它们相同的溢价值加分(都是2%,低于截面均值),但时序标准化算子会给出截然不同的判断:转A的2%处于自身历史分布的极低位置(比均值低很多),是显著的买入信号;转B的2%处于自身历史分布的高位(比均值高),实际上是卖出信号。这才是正确的定价逻辑——同样一个2%的溢价率绝对值,在不同转债身上的含义完全不同。

最后回到文章开头的问题:双低因子失效了吗?不是因子本身失效了,而是我们衡量”低”的标尺需要与时俱进。在转债市场从粗放扩容走向精细定价的今天,用绝对数值来衡量溢价率已经不够用了。时序标准化算子把”相对于谁”和”相对于什么时候”这两个维度同时纳入考量,让双低因子重新获得了选债能力。

需要说明的是,本文讨论的时序标准化思路同样适用于其他转债因子,比如价格、纯债YTM、平价偏离度等。只要因子值存在非平稳性(即分布随时间变化),时序标准化就有用武之地。这是一个工具箱级别的改进,而不仅仅是某一个因子的补丁。

常见问题(FAQ)

Q1:时序标准化算子适用于所有可转债因子吗?

只要因子值存在非平稳性(即分布随时间漂移),时序标准化就有价值。本文以转股溢价率为例,但同样的思路完全适用于纯债YTM、平价偏离度、隐含波动率等因子。唯一的前提是因子需有足够的历史数据(建议≥60个交易日)来计算滚动统计量。

Q2:120天的回看窗口是固定的吗?不同市场环境下需要调整吗?

120天(约6个月)是经验最优值,但并非不可调整。在转债市场高波动期(如2022年3-5月),缩短至90天可以更快适应分布迁移;在低波动期(如2024年下半年),延长至150天能获得更稳定的统计量。建议在实盘中对60/90/120/180天做敏感性测试,选择当前周期IC最稳定的窗口。

Q3:MAD截尾和Z-Score截尾到[-3,3],这两个阈值如何确定?

MAD截尾阈值5.0倍是统计学中常用的极端值判定标准(对应正态分布约99.999%置信区间)。Z-Score最终截尾到[-3, 3]则是为了防止下修公告、强赎公告等极端事件产生不可控的因子值。这两个参数已在2020-2026年的完整回测中验证,对结果影响不敏感。

Q4:时序标准化算子会增加多少换手率?交易成本如何控制?

调整后因子双边年化换手率约8.7倍,高于经典双低的6.3倍。增加的年化交易成本约0.8%,但多空收益从年均12.34%提升到18.52%,净收益显著增加。如果对换手率敏感,可以加入换手率惩罚项或降低调仓频率至每周一次。

Q5:新上市转债没有历史数据,Fallback策略可靠吗?

新券上市前20个交易日使用截面Z-Score填充。虽然不如时序标准化精确,但远优于丢弃NaN导致因子缺失。一个更进阶的做法是用同评级、相近剩余期限的转债均值作为先验,在数据积累到20个交易日后切换到时序标准化。


延伸阅读

💬 评论