
可转债凸性因子是衡量可转债价格对无风险利率变化敏感度的核心量化指标。它的本质是“价格-收益率曲线的二阶导数”,用于反映利率变动时可转债价格的“加速变动幅度”——凸性因子越大,意味着利率的小幅变化会引发可转债价格更剧烈的波动,而这种波动通常能正向放大收益。因此,凸性因子是可转债定价、风险对冲与策略构建不可或缺的关键参数。
核心本质:为什么凸性因子重要?
可转债兼具“债券属性”和“期权属性”,凸性因子正是这两种属性共同作用的产物:
- 债券属性基础:普通债券也有凸性,但通常数值较低,主要反映利率变动对固定利息现金流现值的影响。
- 期权属性放大:可转债内含的转股权(相当于看涨期权)是其独特所在。当利率下降时,正股上涨的概率增大,转股权的价值随之增加,这会使得可转债价格的增速快于普通债券,从而显著放大其凸性因子。
- 核心价值:一个为正且数值较大的凸性因子,意味着可转债在“利率下行+正股上涨”的行情中,收益弹性更高;同时,它也能在一定程度上缓冲利率上行时的价格下跌风险,使其跌幅小于普通债券。
关键特性:可转债凸性因子的独特性
-
正向凸性为主:绝大多数可转债的凸性因子为正,这意味着:
- 利率下降1%所带来的价格上涨幅度,会大于利率上升1%所导致的价格下跌幅度。
- 其核心优势在于“涨多跌少”,有助于提升风险调整后的收益。
-
与转股溢价率负相关:
- 转股溢价率越低(可转债价格越接近正股价格),其内含的期权属性就越强,凸性因子也越大。
- 转股溢价率越高(越偏向纯债属性),其凸性因子就越接近普通债券,数值越小。
-
与剩余期限正相关:
- 剩余期限越长,转股权的时间价值就越高,利率变动对期权价值的影响也越显著,因此凸性因子越大。
- 临近到期的可转债(偏纯债属性),其凸性因子会趋近于普通债券的水平。
量化场景应用:凸性因子怎么用?
-
策略筛选:
- 偏好高弹性:选择凸性因子大(通常>50)、转股溢价率适中(10%-30%)、剩余期限1-3年的可转债,这类标的最适合利率下行+正股震荡上行的行情。
- 偏好稳健:选择凸性因子小(通常<30)、转股溢价率低、债底支撑强的可转债,以降低组合对利率波动的敏感度。
-
风险对冲:
- 当可转债组合的整体凸性因子过高时,可以搭配少量低凸性的利率债,以对冲潜在的利率上行风险,平衡组合的整体敏感度。
- 在量化策略中,可将凸性因子与久期(一阶敏感度)相结合,构建“久期-凸性”双因子风控模型,避免因单一利率变动引发大幅回撤。
-
定价修正:
- 传统定价模型(如BS模型)往往未能充分考虑凸性的影响。加入凸性因子后,可以更精准地计算可转债的理论价格,减少定价偏差。
- 例如,当市场利率波动加大时,高凸性可转债的实际价格通常会高于传统模型的定价,这时就需要通过凸性因子来修正预期收益。
凸性因子计算原理
在开始编写代码前,我们需要先明确可转债凸性因子的核心计算公式。以下是量化实战中常用的简化版本,兼顾了计算效率与准确性:
-
核心公式:
其中:
- P:可转债当前市场价格;
- y:对应剩余期限的无风险利率(通常采用国债收益率,简化版中可用市场贴现率代替);
- t:现金流发生的时间(以年为单位);
- CF_t:第
t期现金流(包括债券利息和到期本金,若考虑转股,则包括转股收益。简化版中我们先计算纯债现金流);
- T:可转债剩余期限(年)。
-
简化适配:
- 针对量化实战,我们先实现纯债凸性的计算,这是后续扩展转股权凸性计算的基础核心。
- 无风险利率采用固定值(实际应用中可对接国债收益率曲线接口进行优化)。
- 现金流依据可转债的票面利率和到期还本规则生成。
凸性因子计算步骤1:导入核心依赖
为了实现高效计算,我们将使用强大的数据科学库,例如 Pandas 和 NumPy。
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class ConvertibleBondConvexity:
"""可转债凸性因子计算类(支持单标的/批量计算,纯债凸性核心版)"""
def __init__(self, risk_free_rate=0.03):
"""
初始化计算类
:param risk_free_rate: 无风险利率(默认3%,可替换为对应期限国债收益率)
"""
self.risk_free_rate = risk_free_rate # 无风险利率(年化)
def get_remaining_years(self, maturity_date_str, current_date=None):
"""
计算可转债剩余期限(转换为年数,精确到小数)
:param maturity_date_str: 到期日期(格式:%Y-%m-%d)
:param current_date: 当前日期(默认当前系统日期,格式:%Y-%m-%d)
:return: 剩余期限(年)
"""
if current_date is None:
current_date = datetime.now().strftime("%Y-%m-%d")
# 日期格式转换
maturity_date = datetime.strptime(maturity_date_str, "%Y-%m-%d")
current_date = datetime.strptime(current_date, "%Y-%m-%d")
if maturity_date < current_date:
raise ValueError("到期日期早于当前日期,该可转债已到期")
# 计算剩余天数,转换为年数(按365天/年计算,量化常用简化方式)
remaining_days = (maturity_date - current_date).days
remaining_years = remaining_days / 365.0
return remaining_years, remaining_days
def generate_cash_flows(self, face_value=100, coupon_rate=0.02, remaining_years=5):
"""
生成可转债纯债现金流(利息+到期本金)
:param face_value: 债券面值(默认100元,可转债通用面值)
:param coupon_rate: 票面年利率(默认2%,可替换为标的实际票面利率)
:param remaining_years: 剩余期限(年)
:return: 现金流列表(包含每期现金流、对应时间点)
"""
cash_flows = []
annual_coupon = face_value * coupon_rate # 每年利息现金流
# 1. 生成年度利息现金流(按年付息,量化简化版;实际可改为按半年/季度付息)
for t in range(1, int(np.ceil(remaining_years)) + 1):
# 若剩余期限不足整数年,最后一期利息按实际天数折算
if t > remaining_years:
actual_interest = annual_coupon * (remaining_years - int(remaining_years))
cash_flows.append({"t": remaining_years, "cf": actual_interest})
break
cash_flows.append({"t": t, "cf": annual_coupon})
# 2. 生成到期本金现金流(最后一期付息时同步归还本金)
cash_flows[-1]["cf"] += face_value # 最后一期现金流=利息+本金
return cash_flows
def calculate_convexity(self, cb_price, maturity_date_str, face_value=100,
coupon_rate=0.02, current_date=None):
"""
核心:计算可转债凸性因子
:param cb_price: 可转债当前市场价格(元)
:param maturity_date_str: 到期日期(%Y-%m-%d)
:param face_value: 债券面值(默认100元)
:param coupon_rate: 票面年利率(默认2%)
:param current_date: 当前日期(%Y-%m-%d)
:return: 纯债凸性因子、辅助计算数据(剩余期限、现金流)
"""
# 步骤1:计算剩余期限(年)
remaining_years, _ = self.get_remaining_years(maturity_date_str, current_date)
# 步骤2:生成纯债现金流
cash_flows = self.generate_cash_flows(face_value, coupon_rate, remaining_years)
# 步骤3:计算凸性因子核心求和项
r = self.risk_free_rate
convexity_sum = 0.0
for cf_item in cash_flows:
t = cf_item["t"] # 现金流发生时间(年)
cf = cf_item["cf"] # 当期现金流
# 单个现金流对凸性的贡献值
cf_contribution = (t * (t + 1) * cf) / ((1 + r) ** t)
convexity_sum += cf_contribution
# 步骤4:计算最终凸性因子
convexity = convexity_sum / (cb_price * ((1 + r) ** 2))
# 整理辅助数据(便于后续分析与验证)
aux_data = {
"remaining_years": round(remaining_years, 4),
"cash_flows": cash_flows,
"risk_free_rate": r,
"cb_price": cb_price
}
return round(convexity, 4), aux_data
def batch_calculate_convexity(self, cb_data_df):
"""
批量计算多个可转债凸性因子(适配量化多标的筛选)
:param cb_data_df: 可转债数据DataFrame(需包含指定列)
:return: 新增凸性因子的DataFrame
"""
if not all(col in cb_data_df.columns for col in
["cb_code", "cb_price", "maturity_date", "coupon_rate"]):
raise ValueError("DataFrame必须包含列:cb_code, cb_price, maturity_date, coupon_rate")
# 批量遍历计算
convexity_results = []
for _, row in cb_data_df.iterrows():
try:
convexity, _ = self.calculate_convexity(
cb_price=row["cb_price"],
maturity_date_str=row["maturity_date"],
coupon_rate=row["coupon_rate"]
)
convexity_results.append(convexity)
except Exception as e:
print(f"警告:{row['cb_code']} 凸性计算失败,错误:{str(e)}")
convexity_results.append(np.nan)
# 新增凸性因子列到原DataFrame
cb_data_df["convexity"] = convexity_results
return cb_data_df
凸性因子计算步骤2:单标的计算示例(快速验证)
掌握基础后,如何将代码用于实际计算?我们先从一个具体的例子开始。
if __name__ == "__main__":
# 1. 初始化凸性计算对象(可修改无风险利率)
convexity_calculator = ConvertibleBondConvexity(risk_free_rate=0.028) # 无风险利率设为2.8%
# 2. 单标的参数(模拟某可转债真实数据)
single_cb_params = {
"cb_price": 125.6, # 当前市场价格
"maturity_date_str": "2030-12-31", # 到期日期
"coupon_rate": 0.018, # 票面年利率1.8%
"face_value": 100 # 面值100元
}
# 3. 计算单标的凸性因子
convexity, aux_data = convexity_calculator.calculate_convexity(
cb_price=single_cb_params["cb_price"],
maturity_date_str=single_cb_params["maturity_date_str"],
coupon_rate=single_cb_params["coupon_rate"],
face_value=single_cb_params["face_value"]
)
# 4. 打印结果
print("="*50)
print("单可转债凸性因子计算结果")
print("="*50)
print(f"最终纯债凸性因子:{convexity}")
print(f"剩余期限(年):{aux_data['remaining_years']}")
print(f"无风险利率(年化):{aux_data['risk_free_rate']:.4f}")
print(f"现金流明细:")
for cf in aux_data["cash_flows"]:
print(f" 时间{cf['t']:<6}:{cf['t']:.2f}年,现金流:{cf['cf']:.2f}元")
凸性因子计算步骤3:多标的批量计算示例(量化场景核心)
真正的量化分析需要处理大量数据。我们的计算类同样支持高效的批量操作,这是量化因子研究的关键环节。
# 5. 批量计算示例(构造多可转债数据,适配量化股池)
cb_batch_data = pd.DataFrame({
"cb_code": ["123001.SZ", "123002.SZ", "123003.SZ", "123004.SZ"],
"cb_price": [118.5, 132.2, 109.8, 145.6],
"maturity_date": ["2029-06-30", "2031-03-15", "2028-09-20", "2032-12-01"],
"coupon_rate": [0.015, 0.022, 0.019, 0.025],
"stock_code": ["300001.SZ", "300002.SZ", "300003.SZ", "300004.SZ"]
})
# 批量计算凸性因子
cb_batch_result = convexity_calculator.batch_calculate_convexity(cb_batch_data)
# 打印批量结果(便于量化筛选)
print("\n" + "="*50)
print("多可转债批量凸性计算结果")
print("="*50)
print(cb_batch_result[["cb_code", "cb_price", "maturity_date", "coupon_rate", "convexity"]])
# 6. 量化筛选:筛选凸性因子>50的高弹性可转债
high_convexity_cb = cb_batch_result[cb_batch_result["convexity"] > 50].copy()
print("\n" + "="*50)
print("高凸性可转债筛选结果(凸性>50)")
print("="*50)
print(high_convexity_cb[["cb_code", "convexity"]])
补充说明:与久期的区别(避免混淆)
很多人容易混淆凸性因子与久期,它们实际上是“一阶敏感度 vs 二阶敏感度”的关系。为了清晰理解,我们可以对比两者的核心差异:
| 指标 |
核心含义 |
通俗理解 |
关联关系 |
| 久期 |
价格对利率的“一阶敏感度” |
利率变动1%时,价格的“线性变动幅度” |
久期是基础,凸性是对久期的补充 |
| 凸性因子 |
价格对利率的“二阶敏感度” |
利率变动1%时,价格的“加速变动幅度” |
凸性越大,久期的动态变化越明显 |
简单来说,久期告诉你价格会变动多少,而凸性则告诉你这种变动的速度(加速度)如何。
总结
可转债凸性因子的核心价值在于捕捉“利率变动对价格的加速影响”,这一特性由债券和期权的双重属性共同决定,是量化交易中进行标的筛选、风险控制和精准定价的关键工具。在实战应用中,必须结合转股溢价率、剩余期限、债底支撑等多个指标进行综合判断,避免单一依赖凸性因子(例如,高凸性的可转债若转股溢价率过高,也可能存在估值泡沫风险)。
风险提示:本文内容及代码示例仅作为金融量化知识的分享与学习交流,模拟测试结果不构成任何投资建议,亦不作为投资决策依据。对金融工程和算法/数据结构的深入学习,有助于我们更好地解析市场模型。更多Python量化金融相关的深度内容,欢迎在云栈社区探索和交流。