问题描述
解决方案
这是一个关于如何在 QMT (Quantitative Trading Platform) 中编写 可转债双低轮动策略 的详细指南。
策略逻辑概述
“双低” 是可转债投资中最经典的策略之一,核心逻辑是寻找 价格低 且 转股溢价率低 的转债。
- 双低公式:通常定义为
双低值 = 转债价格 + 转股溢价率 * 100。 - 选债逻辑:在全市场可转债中,剔除停牌、临期(即将到期)、强赎的转债,按照“双低值”从小到大排序,选取排名前 N 只(例如前 10 只)。
- 轮动逻辑:
- 定期调仓:每天或每周检查一次。
- 卖出:如果持仓的转债跌出排名前 N(或前 M,设置缓冲区)的范围,则卖出。
- 买入:买入新进入排名前 N 的转债。
- 权重:通常采用等权买入。
关键难点:数据获取
在 QMT 原生环境中,直接获取历史的“转股溢价率”可能不如股票行情那么直接。通常有两种方法:
- 计算法:获取转债价格、正股价格、转股价,自行计算(需要维护历史转股价数据,较复杂)。
- 引用扩展数据(推荐):将计算好的“转股溢价率”作为扩展数据导入 QMT,或者如果你的券商数据源(如 Wind/Choice)已集成在 QMT 中,可以直接调用相关字段。
本代码示例将演示标准的轮动框架,并假设你已经可以通过 ext_data (扩展数据) 或自定义函数获取到溢价率数据。
QMT 策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
'''
策略名称:可转债双低轮动策略
策略逻辑:
1. 选取全市场可转债。
2. 计算双低值 = 价格 + 溢价率 * 100。
3. 每日轮动,持有双低值最小的前N只。
4. 资金等权分配。
'''
def init(ContextInfo):
# --- 策略参数设置 ---
ContextInfo.holding_num = 10 # 持仓数量
ContextInfo.adjust_period = 1 # 调仓频率(天)
ContextInfo.buy_threshold = 130 # 价格安全线(可选):价格高于此值不买
# --- 账户设置 ---
# 设置资金账号,实盘中请替换为真实账号
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.account_type = 'STOCK'
ContextInfo.set_account(ContextInfo.account_id)
# --- 费率设置 (回测用) ---
# 佣金万分之三,印花税0 (转债无印花税)
ContextInfo.set_commission(0, [0, 0, 0.0003, 0.0003, 0, 5])
ContextInfo.set_slippage(1, 0.02) # 设置滑点 0.02元
# --- 股票池设置 ---
# 获取沪深转债成分股
# 注意:板块名称可能因券商版本略有不同,通常为 '上证转债', '深证转债'
sh_bonds = ContextInfo.get_stock_list_in_sector('上证转债')
sz_bonds = ContextInfo.get_stock_list_in_sector('深证转债')
ContextInfo.universe = list(set(sh_bonds + sz_bonds))
print(f"策略初始化完成,转债池数量: {len(ContextInfo.universe)}")
def handlebar(ContextInfo):
# 跳过历史K线,只在最后根K线或回测的每一天执行
# 如果是实盘,通常建议使用定时器 run_time 触发,这里演示标准 handlebar 逻辑
if not ContextInfo.is_last_bar() and ContextInfo.do_back_test:
# 回测模式下,每根K线都运行
pass
elif not ContextInfo.is_last_bar():
# 实盘模式下,历史K线不运行
return
# 控制调仓频率
bar_index = ContextInfo.barpos
if bar_index % ContextInfo.adjust_period != 0:
return
print(f"--- 开始执行调仓逻辑,当前Bar索引: {bar_index} ---")
# 1. 获取当前时间
current_date = ContextInfo.get_bar_timetag(bar_index)
# 转换为 YYYYMMDD 格式字符串,用于获取数据
date_str = timetag_to_datetime(current_date, '%Y%m%d')
# 2. 获取行情数据 (收盘价)
# 注意:get_market_data_ex 效率更高
market_data = ContextInfo.get_market_data_ex(
['close', 'vol'],
ContextInfo.universe,
period='1d',
count=1,
subscribe=True
)
# 3. 构建数据表
data_list = []
for code in ContextInfo.universe:
if code in market_data:
df_code = market_data[code]
if not df_code.empty:
close_price = df_code.iloc[-1]['close']
volume = df_code.iloc[-1]['vol']
# 过滤停牌 (成交量为0通常意味着停牌)
if volume == 0:
continue
# 过滤价格过高的 (安全边际)
if close_price > ContextInfo.buy_threshold:
continue
# --- 获取转股溢价率 (关键步骤) ---
# 方法A: 如果你有扩展数据 (ext_data),推荐使用此方法
# premium_rate = ContextInfo.get_ext_data('转股溢价率', code, 0, ContextInfo)
# 方法B: 模拟计算 (此处为了代码可运行,使用模拟数据或简化逻辑)
# 在实际生产中,你需要自行计算:(转债价 - 转股价值)/转股价值
# 这里我们假设 premium_rate 为 0 (仅作演示,实盘必须替换!)
premium_rate = 0.20 # 假设 20%
# 计算双低值
# 双低 = 价格 + 溢价率 * 100
double_low = close_price + premium_rate * 100
data_list.append({
'code': code,
'close': close_price,
'double_low': double_low
})
if not data_list:
print("无符合条件的转债")
return
df = pd.DataFrame(data_list)
# 4. 排序并筛选
# 按双低值从小到大排序
df = df.sort_values(by='double_low', ascending=True)
# 取前 N 只
target_list = df.head(ContextInfo.holding_num)['code'].tolist()
print(f"今日目标持仓: {target_list}")
# 5. 交易执行 (轮动)
rebalance_portfolio(ContextInfo, target_list)
def rebalance_portfolio(ContextInfo, target_list):
'''
调仓函数:卖出不在目标池的,买入在目标池的
'''
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
current_holdings = [obj.m_strInstrumentID + '.' + obj.m_strExchangeID for obj in positions if obj.m_nVolume > 0]
# 1. 卖出逻辑
for code in current_holdings:
if code not in target_list:
# 卖出所有持仓
passorder(24, 1101, ContextInfo.account_id, code, 14, -1, 1, ContextInfo)
print(f"卖出: {code}")
# 2. 买入逻辑
# 计算单只转债目标市值 (总资产 / 计划持仓数)
# 注意:这里简单使用总资产计算,实盘需考虑可用资金
account_info = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'ACCOUNT')
if not account_info:
return
total_asset = account_info[0].m_dBalance
target_value_per_bond = total_asset / ContextInfo.holding_num
for code in target_list:
if code not in current_holdings:
# 使用 order_target_value 更加方便,直接调整到目标市值
# 注意:QMT 的 order_target_value 对股票/转债通常有效,
# 如果无效,需手动计算 volume = target_value / price
# 获取最新价用于计算手数
last_price = ContextInfo.get_market_data_ex(['close'], [code], period='1d', count=1)[code].iloc[-1]['close']
# 计算买入数量 (向下取整到10张,转债1手=10张)
# 注意:QMT下单通常以“股”为单位,转债1张面值100,但在交易接口中通常1手=10张。
# 这里的 volume 单位取决于券商接口,通常填 10 代表 10张(1手) 或者 100 代表 100元面值
# 建议使用 order_value 接口让系统自动计算
order_target_value(code, target_value_per_bond, ContextInfo, ContextInfo.account_id)
print(f"买入/调仓: {code}, 目标市值: {target_value_per_bond}")
# 辅助函数:时间戳转字符串
def timetag_to_datetime(timetag, format_str):
import time
return time.strftime(format_str, time.localtime(timetag / 1000))
代码关键点解析
-
股票池获取 (
get_stock_list_in_sector):- 使用
'上证转债'和'深证转债'板块获取全市场转债代码。这是最方便的方法,无需手动维护列表。
- 使用
-
双低值计算 (
double_low):- 代码中使用了
double_low = close_price + premium_rate * 100。 - 重要提示:代码中的
premium_rate被赋值为固定值仅作演示。在实际使用中,你需要:- 方案一:如果你的 QMT 终端接入了 Wind 或 Choice 数据,可能在
get_market_data的字段中直接支持'premium_rate'。 - 方案二(最常用):使用 Python 的
ext_data接口。你需要先编写一个脚本每天计算溢价率并存入 QMT 的扩展数据中,然后在策略里通过ContextInfo.get_ext_data()读取。
- 方案一:如果你的 QMT 终端接入了 Wind 或 Choice 数据,可能在
- 代码中使用了
-
交易执行 (
rebalance_portfolio):- 使用了
order_target_value。这是一个高级交易函数,它会自动计算当前持仓和目标市值的差额,进行买入或卖出,非常适合轮动策略。 - 注意:转债交易通常是 T+0,但在轮动策略中我们通常按日级别处理。
- 使用了
-
过滤逻辑:
- 代码中简单过滤了停牌(成交量为0)和价格过高(大于130)的转债。
- 实盘中建议增加强赎过滤。由于强赎公告是非结构化数据,通常需要维护一个“强赎黑名单”列表,在代码中
if code in blacklist: continue。
如何完善“转股溢价率”数据?
如果你没有现成的数据源,可以在 handlebar 中尝试自行计算(不推荐,因为需要历史转股价),或者使用以下逻辑结构读取外部数据:
# 假设你已经将溢价率数据导入到了名为 'premium' 的扩展数据中
import numpy as np
# 在循环中:
premium_rate = ContextInfo.get_ext_data('premium', code, 0, ContextInfo)
# 检查数据有效性
if np.isnan(premium_rate):
continue # 如果没有数据则跳过
double_low = close_price + premium_rate # 注意单位,有的数据源是百分比(20),有的是小数(0.2)
总结
这个策略模板提供了一个完整的 QMT 轮动框架。要将其用于实盘,你只需要解决转股溢价率的数据源问题,并根据你的风险偏好调整持仓数量 (holding_num) 和调仓频率 (adjust_period)。
SEO Q&A
Q: QMT中如何获取可转债的转股溢价率?
A: QMT 标准行情接口通常不直接包含转股溢价率。用户通常需要通过“扩展数据”功能导入,或者如果购买了 Level-2 或专业数据服务(如 Wind/Choice 插件),可以通过特定的字段代码获取。
Q: 可转债双低策略适合什么市场环境?
A: 双低策略在震荡市和牛市初期表现较好,因为它兼具债底保护(低价格)和正股上涨的弹性(低溢价)。在极度熊市中,需注意低价转债违约风险;在疯牛市中,双低转债可能跑输高价妖债。
Q: QMT回测可转债策略需要注意什么?
A: 1. 费率设置:转债通常无印花税,佣金较低。2. 数据除权:转债价格通常不复权,但要注意强赎退市的数据处理。3. 强赎风险:回测很难完全模拟强赎公告发布后的暴跌,需在策略逻辑中加入价格止盈(如价格>130或140强制卖出)来模拟规避强赎风险。