问题描述
解决方案
这是一个基于PTrade平台的可转债折价套利策略(即当可转债价格低于转股价值时买入)。
策略逻辑说明
- 核心原理:
- 转股价值 = (可转债面值 / 转股价) * 正股价格
- 溢价率 = (可转债价格 - 转股价值) / 转股价值
- 套利机会:当溢价率为负数(即折价)时,理论上存在套利空间。
- 交易逻辑:
- 买入:当可转债出现折价(溢价率 < 0,或设置一个更低的阈值如 -1%),且处于交易状态时,买入该可转债。
- 卖出:当持仓的可转债不再折价(溢价率 > 0),或者溢价率回归到一定水平时,卖出平仓。
- 风控与资金管理:
- 限制最大持仓数量(例如最多持仓10只),防止过度分散或资金不足。
- 等权重分配资金。
PTrade 策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
策略初始化函数
"""
# 设置最大持仓数量
g.max_holdings = 10
# 设置买入阈值:溢价率小于此值时买入 (例如 -0.005 代表折价0.5%)
g.buy_threshold = -0.001
# 设置卖出阈值:溢价率大于此值时卖出 (例如 0.01 代表溢价1%)
g.sell_threshold = 0.01
# 设定交易费率 (可转债费率通常较低,这里设为万分之一)
set_commission(commission_ratio=0.0001, min_commission=1.0, type='bond')
# 每天10:00运行一次策略逻辑 (避开开盘集合竞价波动)
run_daily(context, trade_logic, time='10:00')
# 打印日志
log.info("可转债折价套利策略已启动")
def trade_logic(context):
"""
每日交易主逻辑
"""
# 1. 获取所有可转债的基础信息
# get_cb_info 返回 DataFrame,包含 bond_code, stock_code, convert_price 等字段
cb_info_df = get_cb_info()
if cb_info_df is None or cb_info_df.empty:
log.warning("未获取到可转债基础信息")
return
# 2. 获取可转债和正股的实时行情快照
# 提取所有可转债代码
bond_list = cb_info_df['bond_code'].tolist()
# 提取所有正股代码
stock_list = cb_info_df['stock_code'].tolist()
# 获取快照数据 (包含最新价 last_px)
# 注意:一次获取大量快照可能会比较慢,实盘中建议分批或使用订阅
bond_snapshots = get_snapshot(bond_list)
stock_snapshots = get_snapshot(stock_list)
potential_buys = []
# 3. 遍历计算溢价率
for index, row in cb_info_df.iterrows():
bond_code = row['bond_code']
stock_code = row['stock_code']
convert_price = row['convert_price'] # 转股价
# 过滤掉数据不全的标的
if bond_code not in bond_snapshots or stock_code not in stock_snapshots:
continue
bond_data = bond_snapshots[bond_code]
stock_data = stock_snapshots[stock_code]
# 获取最新价格
bond_price = bond_data.get('last_px', 0)
stock_price = stock_data.get('last_px', 0)
# 过滤停牌或无成交的标的
if bond_price <= 0 or stock_price <= 0 or convert_price <= 0:
continue
# 过滤交易状态异常的 (HALT: 暂停, SUSP: 停盘)
if bond_data.get('trade_status') in ['HALT', 'SUSP', 'STOPT']:
continue
# 计算转股价值 = (100 / 转股价) * 正股价格
convert_value = (100.0 / convert_price) * stock_price
# 计算溢价率 = (债价 - 转股价值) / 转股价值
premium_rate = (bond_price - convert_value) / convert_value
# 记录数据
potential_buys.append({
'bond_code': bond_code,
'premium_rate': premium_rate,
'bond_price': bond_price
})
# 转换为DataFrame方便排序
if not potential_buys:
log.info("今日无有效可转债数据")
return
df_candidates = pd.DataFrame(potential_buys)
# 4. 卖出逻辑:检查持仓,如果溢价率回归(变高),则卖出
current_positions = context.portfolio.positions
for bond_code in list(current_positions.keys()):
# 获取该持仓的最新溢价率
record = df_candidates[df_candidates['bond_code'] == bond_code]
if not record.empty:
current_premium = record.iloc[0]['premium_rate']
# 如果溢价率高于卖出阈值,止盈/止损卖出
if current_premium > g.sell_threshold:
order_target(bond_code, 0)
log.info("卖出 %s, 当前溢价率: %.2f%%, 触发卖出阈值" % (bond_code, current_premium * 100))
else:
# 如果取不到数据(可能停牌或退市),视情况处理,这里暂不操作
pass
# 5. 买入逻辑
# 筛选出满足买入阈值(折价)的标的
buy_candidates = df_candidates[df_candidates['premium_rate'] < g.buy_threshold]
# 按溢价率从小到大排序(折价越深越优先)
buy_candidates = buy_candidates.sort_values(by='premium_rate', ascending=True)
# 获取当前持仓数量
current_hold_count = len(context.portfolio.positions)
available_slots = g.max_holdings - current_hold_count
if available_slots > 0 and not buy_candidates.empty:
# 计算单只标的可用资金
cash = context.portfolio.cash
if cash < 1000: # 资金太少不交易
return
# 简单的资金分配:剩余资金 / 剩余仓位
# 注意:实际交易中可能需要更复杂的资金管理
per_stock_cash = cash / available_slots
for index, row in buy_candidates.iterrows():
if available_slots <= 0:
break
target_bond = row['bond_code']
# 如果已经持仓,跳过
if target_bond in current_positions and current_positions[target_bond].amount > 0:
continue
# 下单买入
# order_value 会自动向下取整到手(10张/1000元面值)
order_id = order_value(target_bond, per_stock_cash)
if order_id:
log.info("买入 %s, 价格: %.3f, 溢价率: %.2f%%" % (
target_bond, row['bond_price'], row['premium_rate'] * 100))
available_slots -= 1
def handle_data(context, data):
"""
盘中运行函数,本策略主要逻辑在 run_daily 中执行,此处留空或用于风控
"""
pass
代码关键点解析
- 数据获取 (
get_cb_info):- 这是PTrade特有的API,用于获取可转债的静态数据(如转股价
convert_price和对应的正股代码stock_code)。这是计算转股价值的基础。
- 这是PTrade特有的API,用于获取可转债的静态数据(如转股价
- 行情快照 (
get_snapshot):- 策略使用
get_snapshot获取最新的last_px(最新价)。相比get_price或get_history,快照数据在盘中交易时延更低,更适合监控实时折价机会。
- 策略使用
- 溢价率计算:
- 代码中严格按照公式计算:
premium_rate = (bond_price - convert_value) / convert_value。 - 当
premium_rate为负数时,表示折价(可转债价格便宜,理论上买入转债转股有利可图,或者等待价格回归)。
- 代码中严格按照公式计算:
- 交易执行:
- 使用
order_value按金额下单,系统会自动处理最小交易单位(可转债通常为10张,即面值1000元)。 - 加入了
trade_status检查,避免买入停牌的债券。
- 使用
注意事项
- 回测与实盘:此代码可直接用于回测。如果在实盘中使用,建议增加更严格的异常处理(如网络超时重试)。
- 转股操作:本策略是基于价格回归的逻辑(低买高卖)。如果你想进行真正的“转股套利”(即买入债 -> 申请转股 -> 卖出股票),需要调用
debt_to_stock_order接口。但请注意,转股通常是 T+1 可卖(当日买入转债,当日申请转股,次日股票到账才能卖),因此面临隔夜持仓风险,并非无风险套利。 - 强赎风险:折价的可转债往往价格较高或临近到期,需注意是否触发“强赎”条款。可以通过
get_cb_info中的其他字段或外部数据来过滤即将强赎的标的。
Q&A
Q: 为什么不在 handle_data 里每分钟运行?
A: get_cb_info 和全市场 get_snapshot 数据量较大,每分钟运行可能会导致回测速度极慢或触发API流控限制。对于折价套利,通常日内机会持续时间较长,或者在特定时间点(如上午10点或下午2点)检查即可。如果需要更高频,可以缩小股票池范围。
Q: 如何实现真正的 T+0 转股套利?
A: 目前A股市场可转债转股是 T+1 的。所谓的 T+0 套利通常是指拥有底仓(融券卖出正股,买入转债转股还券)或者利用转债本身的 T+0 交易属性做日内波段。本策略实现的是基于折价率的趋势回归策略。
Q: set_commission 中的 type='bond' 是什么意思?
A: 这是设置交易费率的类型。可转债的交易费用通常远低于股票,且没有印花税。在回测中设置准确的费率对套利策略(通常利润微薄)非常重要。