一、背景
在使用最小二乘法蒙特卡洛模拟对可转债进行定价过程中,可转债特殊条款的设置,是在路径遍历过程中较为复杂的地方,不同转债有不同的条款设计,有的更有利于发行人,有的设置更能吸引投资者。从日常工作角度,需要快速修改参数,实现对可转债的定价。
从上一篇文章《基于最小二乘法蒙特卡洛模拟(LSM)可转债定价原理及算法演示》中关于可转债特殊条款的介绍,可以认为(有条件赎回,强制转股)和(有条件回售,下修行权价)作为条款博弈的组合。
本篇即是便于参数修改和应用的角度,以上两对博弈条款进行实现。
二、条款实现
1、有条件赎回和强制转股
有条件赎回:在转股期限内,股票在任何连续【30】个交易日中至少【15】个交易日的收盘价格不低于当期转股价格的 【130%】。公司可按照面额+当期应计利息赎回转债。公司通过赎回,迫使投资人完成转股。
在触发有条件赎回条款时,此时往往转股价值大于赎回价格,有条件赎回条款仅仅是迫使投资者转股的条款,投资者也往往选择转股,因此可以认为赎回不会发生。
class CallClause(CBClause):
def __init__(self, enabled=True, trigger_days=30, up_percent=0.3, required_days=30,
conversion_period_start=126, conversion_period_end=1512):
super().__init__(enabled)
self.observation_window = trigger_days # 观察窗口长度
self.up_percent = up_percent # 股价超过转股价的幅度(百分比,如0.3表示30%)
self.required_days = required_days# 需满足触发条件的天数
self.conversion_period_start = conversion_period_start # 转股期开始时间
self.conversion_period_end = conversion_period_end # 转股期结束时间
self.trigger_counter = 0 # 触发计数器
self.trigger_threshold = required_days # 触发阈值
self.call_count = 0 # 记录触发次数
self.call_path = [] # 记录触发赎回路径
def is_in_conversion_period(self, current_step):
"""判断是否在转股期内"""
# 整个观察窗口都需要在转股期内
return self.conversion_period_start + self.observation_window - 1 <= current_step <= self.conversion_period_end
def check_trigger(self, path_data, current_step, path_conversion_price):
self.trigger_counter = 0
if not self.enabled:
return False
# 判断整个观察窗口是否在转股期内
if not self.is_in_conversion_period(current_step):
return False
# 检查观察窗口内是否满足触发条件
recent_prices = path_data[current_step - (self.observation_window - 1):current_step + 1]
conversion_price = path_conversion_price[current_step - (self.observation_window - 1):current_step + 1]
# 计算触发天数
for p, cp in zip(recent_prices, conversion_price):
if p >= cp * (1 + self.up_percent):
self.trigger_counter += 1
if self.trigger_counter >= self.required_days:
return True
return False
def process_redemption(self, s_data, cashflows, conversion_price_table):
n_paths, n_steps = s_data.shape
for path_idx in range(n_paths):
path_data = s_data[path_idx, :] # 取当前路径的股价数据
path_conversion_price = conversion_price_table[path_idx, :] # 取当前路径的转股价数据
# 时点0为初始节点,从恰好进入观察窗口开始遍历
for step in range(self.conversion_period_start + self.observation_window - 1, n_steps+1):
# 检查当前路径是否满足赎回条款的触发条件
trigger_result = self.check_trigger(path_data, step, path_conversion_price)
if trigger_result:
# 记录触发信息
self.call_path.append(path_idx)
cashflows[path_idx, step] = 100 * path_data[step] / path_conversion_price[step]
self.call_count += 1
break
print('已处理赎回条款')
return cashflows
2、有条件回售和下修转股价
上市公司发行可转债的目的是为了进行股权融资,因此可以认为在触发有条件回售条款时,上市公司往往下修行权价,有条件回售不会发生,而仅仅触发转股价下修条款时,发行人并不会下修行权价。这里需要注意的是,需要先遍历检测有条件回售和下修转股价,然后再遍历是否触发有条件赎回。同时,在遍历是否触发有条件回售时,在触发后需要继续遍历,即当前路径有可能多次触发有条件回售而下修转股价。而且需要注意,可转债条款中会有如下约定“如果出现转股价格向下修正的情况,则上述连续三十个交易日须从转股价格调整之后的第一个交易日起重新计算。”
class PutClause(CBClause):
def __init__(self, enabled=True, trigger_days=30, down_percent=0.3, required_days=15,
put_period_start=126, put_period_end=1512):
super().__init__(enabled)
self.observation_window = trigger_days # 观察窗口长度
self.down_percent = down_percent # 股价低于转股价幅度(0-1)
self.required_days = required_days # 需满足触发条件的天数
self.put_period_start = put_period_start # 回售期开始时间
self.put_period_end = put_period_end # 回售期结束时间
self.put_counter = 0 # 触发计数器
self.put_path = [] # 记录触发回售路径
def is_in_put_period(self, current_step):
return self.put_period_start + self.observation_window - 1 <= current_step <= self.put_period_end
def check_trigger(self, path_data, current_step, conversion_price):
if not self.is_in_put_period(current_step):
return False
# 检查观察窗口内是否满足触发条件
recent_prices = path_data[current_step - (self.observation_window - 1):current_step + 1]
recent_conversion_prices = conversion_price[current_step - (self.observation_window - 1):current_step + 1]
# 检查股价是否低于转股价的(1-down_percent)倍
trigger_days = sum(p < cp * (1 - self.down_percent)
for p, cp in zip(recent_prices, recent_conversion_prices))
if trigger_days >= self.required_days:
return True
return False
def process_put(self, s_data, conversion_price_table):
n_paths, n_steps = s_data.shape
for path_idx in range(n_paths):
path_data = s_data[path_idx, :]
path_conversion_price = conversion_price_table[path_idx, :]
step = self.put_period_start + self.observation_window - 1
while step <= n_steps:
# 检查当前路径是否满足赎回条款的触发条件
# 同一路径可能多次触发
trigger_result = self.check_trigger(path_data, step, path_conversion_price)
if trigger_result:
avg_price = np.mean(path_data[step - 19:step + 1])
current_price = path_data[step]
new_conversion_price = max(avg_price, current_price)
conversion_price_table[path_idx, step + 1:] = new_conversion_price
self.put_path.append(path_idx)
# 触发后跳过30个时间步长,“如果出现转股价格向下修正的情况,则上述连续三十个交易日须从转股价格调整之后的第一个交易日起重新计算。”
step += 30
continue
step += 1 # 正常情况每次递增1
print('已处理回售条款')
return conversion_price_table
9611

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



