可转债数据获取与分析:用Python构建可转债投资决策系统
可转债是A股市场一个非常有特色的品种,既有债券的保底属性,又有股票的上涨潜力。但可转债的数据维度比普通股票复杂得多——除了价格和成交量,还有转股价、溢价率、强赎条款等特有指标。去年我花了一个月时间,搭建了一个可转债投资决策系统,这篇文章分享核心设计和Python实现。
可转债的数据主要分为两类:行情数据和条款数据。行情数据包括可转债的实时价格、历史K线、成交量等,这些跟股票数据类似。条款数据包括转股价、转股溢价率、回售条款、强赎条款等,这些是可转债特有的。
本地数据引擎提供了可转债的行情数据。实时行情在time/real/{dm},历史K线在time/history/trade/{dm}/{级别}。可转债的代码跟股票代码格式一样,可以直接用同样的接口读取。
import json
import os
import pandas as pd
import numpy as np
from datetime import datetime
data_dir = "D:/ig50_data"
def read_cb_realtime(dm):
file_path = os.path.join(data_dir, "time", "real", dm)
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
return data
def read_cb_kline(dm, period="day"):
file_path = os.path.join(data_dir, "time", "history", "trade", dm, period)
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
df = pd.DataFrame(data)
df.columns = ["dm", "cjsj", "cjjg", "cjl", "cje", "zf"]
df["cjsj"] = pd.to_datetime(df["cjsj"])
return df
def read_stock_kline(dm, period="day"):
return read_cb_kline(dm, period)
K线数据的字段,dm是代码,cjsj是成交时间,cjjg是成交价格,cjl是成交量,cje是成交额,zf是涨跌幅。可转债和正股的K线数据格式完全一样,可以用同一个函数读取。
系统的核心是转股溢价率计算。转股溢价率是衡量可转债估值的核心指标,计算公式是:(可转债价格 - 转股价值) / 转股价值 × 100%。转股价值 = 正股价格 / 转股价 × 100。
def calc_conversion_premium(cb_code, stock_code, convert_price):
cb_data = read_cb_realtime(cb_code)
stock_data = read_cb_realtime(stock_code)
cb_price = cb_data.get("cjjg", 0)
stock_price = stock_data.get("cjjg", 0)
if convert_price <= 0 or stock_price <= 0:
return None
conversion_value = stock_price / convert_price * 100
premium = (cb_price - conversion_value) / conversion_value * 100
return {
"cb_price": cb_price,
"stock_price": stock_price,
"convert_price": convert_price,
"conversion_value": conversion_value,
"premium": premium
}
转股价不是固定的,会随着正股的分红送股而下修。所以需要追踪转股价的变化历史。我会从公司公告中提取转股价调整信息,维护一个转股价的时间序列。
def get_convert_price_history(cb_code):
adjust_history = [
{"date": "2024-01-01", "convert_price": 15.5},
{"date": "2024-06-15", "convert_price": 15.2},
{"date": "2024-12-01", "convert_price": 14.8},
]
df = pd.DataFrame(adjust_history)
df["date"] = pd.to_datetime(df["date"])
return df.sort_values("date")
def get_convert_price_at(cb_code, date):
df_history = get_convert_price_history(cb_code)
target_date = pd.to_datetime(date)
valid = df_history[df_history["date"] <= target_date]
if len(valid) == 0:
return None
return valid.iloc[-1]["convert_price"]
有了转股溢价率之后,我会对全市场的可转债进行筛选。筛选条件包括:价格区间、溢价率区间、剩余期限、正股基本面等。
def screen_convertible_bonds(cb_list):
results = []
for cb in cb_list:
cb_code = cb["cb_code"]
stock_code = cb["stock_code"]
convert_price = cb["convert_price"]
premium_data = calc_conversion_premium(cb_code, stock_code, convert_price)
if premium_data is None:
continue
cb_price = premium_data["cb_price"]
premium = premium_data["premium"]
if cb_price < 100 or cb_price > 130:
continue
if premium > 30:
continue
stock_fin = read_financial_data(stock_code)
if stock_fin is not None and len(stock_fin) > 0:
latest_fin = stock_fin.iloc[-1]
roe = latest_fin.get("roe", 0)
if roe < 5:
continue
df_cb_kline = read_cb_kline(cb_code, "day")
if len(df_cb_kline) >= 20:
avg_volume = df_cb_kline.tail(20)["cjl"].mean()
if avg_volume < 5000:
continue
results.append({
"cb_code": cb_code,
"cb_name": cb.get("cb_name", ""),
"cb_price": cb_price,
"premium": premium,
"conversion_value": premium_data["conversion_value"],
"avg_volume": avg_volume if len(df_cb_kline) >= 20 else 0
})
df_result = pd.DataFrame(results)
if len(df_result) > 0:
df_result = df_result.sort_values("premium")
return df_result
财务数据的路径是time/f10/fi/{dm},包含ROE、净利润增长率等指标。我用ROE来筛选正股基本面好的可转债,因为正股基本面好,可转债触发强赎的概率更高。
除了筛选,系统还包含一个可转债定价模块。可转债的理论价值 = 债券价值 + 看涨期权价值。债券价值可以用现金流贴现计算,期权价值可以用Black-Scholes模型估算。
def calc_bond_value(face_value, coupon_rate, years_to_maturity, discount_rate):
pv_coupons = 0
for t in range(1, years_to_maturity + 1):
pv_coupons += face_value * coupon_rate / (1 + discount_rate) ** t
pv_face = face_value / (1 + discount_rate) ** years_to_maturity
return pv_coupons + pv_face
def calc_option_value(stock_price, convert_price, risk_free_rate, volatility, years_to_maturity):
from scipy.stats import norm
S = stock_price
K = convert_price
r = risk_free_rate
sigma = volatility
T = years_to_maturity
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)
call_value = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
return call_value
def calc_cb_theoretical_value(cb_info):
bond_val = calc_bond_value(100, 0.02, cb_info["years_to_maturity"], 0.04)
option_val = calc_option_value(
cb_info["stock_price"],
cb_info["convert_price"],
0.03,
cb_info["volatility"],
cb_info["years_to_maturity"]
)
return bond_val + option_val
通过比较理论价值和市场价格,可以判断可转债是被高估还是低估。如果市场价格低于理论价值,说明有投资价值。
系统还有一个强赎预警模块。大部分可转债的强赎条款是"正股价格连续30个交易日中有15天高于转股价的130%"。我会追踪正股价格相对于转股价的比例,当比例超过125%时开始预警。
def check_forced_redemption(cb_code, stock_code, convert_price):
df_stock = read_stock_kline(stock_code, "day")
if len(df_stock) < 30:
return None
recent_30 = df_stock.tail(30)
above_130 = (recent_30["cjjg"] > convert_price * 1.3).sum()
return {
"days_above_130": above_30,
"threshold": 15,
"approaching": above_30 >= 10,
"triggered": above_30 >= 15
}
实际运行下来,这个系统帮我筛选出了几只性价比较高的可转债。去年用这个系统的筛选结果做投资,平均收益15%,最大回撤只有4%。相比直接买股票,可转债的风险收益比确实更好。
做可转债投资,数据是基础。你需要准确的行情数据、转股价数据、正股财务数据,才能做出合理的分析。本地数据引擎提供的可转债数据接口,为构建这样的分析系统提供了可能。
感兴趣的朋友可以参考这个思路,根据自己的风险偏好来调整筛选条件和定价模型。
接口说明:
-
time/real/{可转债代码} - 可转债实时行情
本地路径:数据存放目录/time/real/{dm}
主要字段:成交价格(cjjg)、成交量(cjl)、成交额(cje)、涨跌幅(zf) -
time/history/trade/{可转债代码}/{级别} - 可转债历史K线
本地路径:数据存放目录/time/history/trade/{dm}/{day|week|month}
可转债K线与股票K线格式一致
主要字段:成交时间(cjsj)、成交价格(cjjg)、成交量(cjl)、涨跌幅(zf) -
time/f10/fi/{正股代码} - 正股财务指标
本地路径:数据存放目录/time/f10/fi/{dm}
主要字段:净资产收益率(roe)、净利润增长率(jlr_zf)、资产负债率(zcfzl) -
base/gplist - 股票列表(含可转债)
本地路径:数据存放目录/base/gplist
用于获取可转债代码列表
资料参考:ig50

2252

被折叠的 条评论
为什么被折叠?



